czty commited on
Commit
c9c564e
·
verified ·
1 Parent(s): cd5762a

Add files using upload-large-folder tool

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. Biomni/mcp_generated/mcp_bamtools/Dockerfile +40 -0
  2. Biomni/mcp_generated/mcp_bamtools/app/bamtools_server.py +712 -0
  3. Biomni/mcp_generated/mcp_bamtools/app/bamtools_shim_server.py +55 -0
  4. Biomni/mcp_generated/mcp_bamtools/docker-compose.yml +22 -0
  5. Biomni/mcp_generated/mcp_bamtools/environment.yaml +10 -0
  6. Biomni/mcp_generated/mcp_bamtools/requirements.txt +2 -0
  7. Biomni/mcp_generated/mcp_bioconductor-alabaster.spatial/Dockerfile +40 -0
  8. Biomni/mcp_generated/mcp_bioconductor-alabaster.spatial/docker-compose.yml +22 -0
  9. Biomni/mcp_generated/mcp_bioconductor-alabaster.spatial/environment.yaml +10 -0
  10. Biomni/mcp_generated/mcp_bioconductor-alabaster.spatial/requirements.txt +2 -0
  11. Biomni/mcp_generated/mcp_bioconductor-cardspa/Dockerfile +40 -0
  12. Biomni/mcp_generated/mcp_bioconductor-cardspa/app/bioconductor-cardspa_server.py +189 -0
  13. Biomni/mcp_generated/mcp_bioconductor-cardspa/app/bioconductor-cardspa_shim_server.py +55 -0
  14. Biomni/mcp_generated/mcp_bioconductor-cardspa/app/requirements.txt +1 -0
  15. Biomni/mcp_generated/mcp_bioconductor-cardspa/docker-compose.yml +22 -0
  16. Biomni/mcp_generated/mcp_bioconductor-cardspa/environment.yaml +10 -0
  17. Biomni/mcp_generated/mcp_bioconductor-cardspa/requirements.txt +2 -0
  18. Biomni/mcp_generated/mcp_bioconductor-cellid/Dockerfile +40 -0
  19. Biomni/mcp_generated/mcp_bioconductor-cellid/app/bioconductor-cellid_server.py +288 -0
  20. Biomni/mcp_generated/mcp_bioconductor-cellid/app/bioconductor-cellid_shim_server.py +55 -0
  21. Biomni/mcp_generated/mcp_bioconductor-cellid/docker-compose.yml +22 -0
  22. Biomni/mcp_generated/mcp_bioconductor-cellid/environment.yaml +10 -0
  23. Biomni/mcp_generated/mcp_bioconductor-cellid/requirements.txt +2 -0
  24. Biomni/mcp_generated/mcp_bioconductor-ggspavis/Dockerfile +40 -0
  25. Biomni/mcp_generated/mcp_bioconductor-ggspavis/app/bioconductor-ggspavis_server.py +403 -0
  26. Biomni/mcp_generated/mcp_bioconductor-ggspavis/app/bioconductor-ggspavis_shim_server.py +55 -0
  27. Biomni/mcp_generated/mcp_bioconductor-ggspavis/app/requirements.txt +1 -0
  28. Biomni/mcp_generated/mcp_bioconductor-ggspavis/docker-compose.yml +22 -0
  29. Biomni/mcp_generated/mcp_bioconductor-ggspavis/environment.yaml +10 -0
  30. Biomni/mcp_generated/mcp_bioconductor-ggspavis/requirements.txt +2 -0
  31. Biomni/mcp_generated/mcp_bioconductor-mastr/Dockerfile +40 -0
  32. Biomni/mcp_generated/mcp_bioconductor-mastr/app/bioconductor-mastr_server.py +208 -0
  33. Biomni/mcp_generated/mcp_bioconductor-mastr/app/bioconductor-mastr_shim_server.py +55 -0
  34. Biomni/mcp_generated/mcp_bioconductor-mastr/app/requirements.txt +1 -0
  35. Biomni/mcp_generated/mcp_bioconductor-mastr/requirements.txt +2 -0
  36. Biomni/mcp_generated/mcp_bioconductor-rhdf5filters/Dockerfile +40 -0
  37. Biomni/mcp_generated/mcp_bioconductor-rhdf5filters/app/bioconductor-rhdf5filters_server.py +78 -0
  38. Biomni/mcp_generated/mcp_bioconductor-rhdf5filters/app/requirements.txt +1 -0
  39. Biomni/mcp_generated/mcp_bioconductor-rhdf5filters/docker-compose.yml +22 -0
  40. Biomni/mcp_generated/mcp_bioconductor-rhdf5filters/environment.yaml +10 -0
  41. Biomni/mcp_generated/mcp_bioconductor-rsubread/Dockerfile +40 -0
  42. Biomni/mcp_generated/mcp_bioconductor-rsubread/app/bioconductor-rsubread_server.py +322 -0
  43. Biomni/mcp_generated/mcp_bioconductor-rsubread/app/bioconductor-rsubread_shim_server.py +55 -0
  44. Biomni/mcp_generated/mcp_bioconductor-rsubread/app/requirements.txt +1 -0
  45. Biomni/mcp_generated/mcp_bioconductor-rtracklayer/app/bioconductor-rtracklayer_server.py +340 -0
  46. Biomni/mcp_generated/mcp_bioconductor-scdotplot/Dockerfile +40 -0
  47. Biomni/mcp_generated/mcp_bioconductor-scdotplot/app/bioconductor-scdotplot_server.py +273 -0
  48. Biomni/mcp_generated/mcp_bioconductor-scdotplot/app/bioconductor-scdotplot_shim_server.py +55 -0
  49. Biomni/mcp_generated/mcp_bioconductor-scdotplot/app/requirements.txt +1 -0
  50. Biomni/mcp_generated/mcp_bioconductor-scdotplot/docker-compose.yml +22 -0
Biomni/mcp_generated/mcp_bamtools/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 bamtools via conda (e.g., from bioconda)
14
+ RUN conda install -c bioconda bamtools -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 bamtools_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/bamtools_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/bamtools_server.py"]
40
+
Biomni/mcp_generated/mcp_bamtools/app/bamtools_server.py ADDED
@@ -0,0 +1,712 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import subprocess
2
+ import shlex
3
+ import os
4
+ from pathlib import Path
5
+ from typing import List, Optional
6
+
7
+ # In a real MCP environment, the 'mcp' object with its decorators
8
+ # would be provided by the framework. This is a placeholder for standalone validation.
9
+ class _MCP:
10
+ def tool(self, *args, **kwargs):
11
+ def decorator(f):
12
+ return f
13
+ return decorator
14
+ mcp = _MCP()
15
+
16
+ ### Bamtools Tool Definitions ###
17
+
18
+ from mcp.server.fastmcp import FastMCP
19
+
20
+ SERVER_NAME = 'local_bamtools'
21
+ mcp = FastMCP(SERVER_NAME)
22
+
23
+ @mcp.tool()
24
+ def convert(
25
+ in_bam: Path,
26
+ format: str,
27
+ out: Optional[Path] = None,
28
+ region: Optional[str] = None,
29
+ ) -> dict:
30
+ """
31
+ Converts a BAM file to various other formats.
32
+
33
+ Args:
34
+ in_bam: The input BAM file.
35
+ format: The output format. Must be one of: bed, fasta, fastq, json, pileup, sam, yaml.
36
+ out: The output filename. If not provided, output is sent to stdout.
37
+ region: Only convert alignments that overlap this region (e.g., "ref:start-end").
38
+
39
+ Returns:
40
+ A dictionary containing the command executed, stdout, stderr, and output files.
41
+ """
42
+ if not in_bam.is_file():
43
+ raise FileNotFoundError(f"Input BAM file not found: {in_bam}")
44
+
45
+ valid_formats = {"bed", "fasta", "fastq", "json", "pileup", "sam", "yaml"}
46
+ if format not in valid_formats:
47
+ raise ValueError(f"Invalid format '{format}'. Must be one of {valid_formats}")
48
+
49
+ cmd = ["bamtools", "convert", "-in", str(in_bam), "-format", format]
50
+ output_files = []
51
+
52
+ if out:
53
+ cmd.extend(["-out", str(out)])
54
+ output_files.append(str(out))
55
+ if region:
56
+ cmd.extend(["-region", region])
57
+
58
+ command_executed = shlex.join(cmd)
59
+ try:
60
+ result = subprocess.run(
61
+ cmd,
62
+ capture_output=True,
63
+ text=True,
64
+ check=True,
65
+ )
66
+ return {
67
+ "command_executed": command_executed,
68
+ "stdout": result.stdout,
69
+ "stderr": result.stderr,
70
+ "output_files": output_files,
71
+ }
72
+ except subprocess.CalledProcessError as e:
73
+ return {
74
+ "command_executed": command_executed,
75
+ "stdout": e.stdout,
76
+ "stderr": e.stderr,
77
+ "error": f"bamtools convert failed with exit code {e.returncode}",
78
+ "output_files": [],
79
+ }
80
+
81
+ @mcp.tool()
82
+ def count(in_bams: List[Path]) -> dict:
83
+ """
84
+ Prints the number of alignments in one or more BAM files.
85
+
86
+ Args:
87
+ in_bams: A list of input BAM files.
88
+
89
+ Returns:
90
+ A dictionary containing the command executed, stdout (the count), and stderr.
91
+ """
92
+ if not in_bams:
93
+ raise ValueError("At least one input BAM file must be provided.")
94
+
95
+ cmd = ["bamtools", "count"]
96
+ for bam_file in in_bams:
97
+ if not bam_file.is_file():
98
+ raise FileNotFoundError(f"Input BAM file not found: {bam_file}")
99
+ cmd.extend(["-in", str(bam_file)])
100
+
101
+ command_executed = shlex.join(cmd)
102
+ try:
103
+ result = subprocess.run(
104
+ cmd,
105
+ capture_output=True,
106
+ text=True,
107
+ check=True,
108
+ )
109
+ return {
110
+ "command_executed": command_executed,
111
+ "stdout": result.stdout,
112
+ "stderr": result.stderr,
113
+ "output_files": [],
114
+ }
115
+ except subprocess.CalledProcessError as e:
116
+ return {
117
+ "command_executed": command_executed,
118
+ "stdout": e.stdout,
119
+ "stderr": e.stderr,
120
+ "error": f"bamtools count failed with exit code {e.returncode}",
121
+ "output_files": [],
122
+ }
123
+
124
+ @mcp.tool()
125
+ def coverage(in_bams: List[Path]) -> dict:
126
+ """
127
+ Prints coverage statistics from one or more BAM files.
128
+
129
+ Args:
130
+ in_bams: A list of input BAM files.
131
+
132
+ Returns:
133
+ A dictionary containing the command executed, stdout (coverage stats), and stderr.
134
+ """
135
+ if not in_bams:
136
+ raise ValueError("At least one input BAM file must be provided.")
137
+
138
+ cmd = ["bamtools", "coverage"]
139
+ for bam_file in in_bams:
140
+ if not bam_file.is_file():
141
+ raise FileNotFoundError(f"Input BAM file not found: {bam_file}")
142
+ cmd.extend(["-in", str(bam_file)])
143
+
144
+ command_executed = shlex.join(cmd)
145
+ try:
146
+ result = subprocess.run(
147
+ cmd,
148
+ capture_output=True,
149
+ text=True,
150
+ check=True,
151
+ )
152
+ return {
153
+ "command_executed": command_executed,
154
+ "stdout": result.stdout,
155
+ "stderr": result.stderr,
156
+ "output_files": [],
157
+ }
158
+ except subprocess.CalledProcessError as e:
159
+ return {
160
+ "command_executed": command_executed,
161
+ "stdout": e.stdout,
162
+ "stderr": e.stderr,
163
+ "error": f"bamtools coverage failed with exit code {e.returncode}",
164
+ "output_files": [],
165
+ }
166
+
167
+ @mcp.tool()
168
+ def filter_bam(
169
+ in_bams: List[Path],
170
+ out_bam: Optional[Path] = None,
171
+ region: Optional[str] = None,
172
+ property_filters: Optional[List[str]] = None,
173
+ tag_filters: Optional[List[str]] = None,
174
+ script: Optional[Path] = None,
175
+ list_file: Optional[Path] = None,
176
+ force_compression: bool = False,
177
+ ) -> dict:
178
+ """
179
+ Filters BAM file(s) by user-specified criteria.
180
+
181
+ Args:
182
+ in_bams: The input BAM file(s).
183
+ out_bam: The output BAM file. If not provided, output is sent to stdout.
184
+ region: Only keeps alignments that overlap this region.
185
+ property_filters: List of "TAG:VALUE" strings to filter by BamAlignment property.
186
+ tag_filters: List of "TAG:VALUE" strings to filter by BamAlignment tag.
187
+ script: A file containing a list of filter rules.
188
+ list_file: A file containing a list of read names to be kept.
189
+ force_compression: If True, the output BAM file will be compressed.
190
+
191
+ Returns:
192
+ A dictionary containing the command executed, stdout, stderr, and output files.
193
+ """
194
+ if not in_bams:
195
+ raise ValueError("At least one input BAM file must be provided.")
196
+
197
+ cmd = ["bamtools", "filter"]
198
+ output_files = []
199
+
200
+ for bam_file in in_bams:
201
+ if not bam_file.is_file():
202
+ raise FileNotFoundError(f"Input BAM file not found: {bam_file}")
203
+ cmd.extend(["-in", str(bam_file)])
204
+
205
+ if out_bam:
206
+ cmd.extend(["-out", str(out_bam)])
207
+ output_files.append(str(out_bam))
208
+ if region:
209
+ cmd.extend(["-region", region])
210
+ if property_filters:
211
+ for p_filter in property_filters:
212
+ cmd.extend(["-property", p_filter])
213
+ if tag_filters:
214
+ for t_filter in tag_filters:
215
+ cmd.extend(["-tag", t_filter])
216
+ if script:
217
+ if not script.is_file():
218
+ raise FileNotFoundError(f"Script file not found: {script}")
219
+ cmd.extend(["-script", str(script)])
220
+ if list_file:
221
+ if not list_file.is_file():
222
+ raise FileNotFoundError(f"List file not found: {list_file}")
223
+ cmd.extend(["-list", str(list_file)])
224
+ if force_compression:
225
+ cmd.append("-forceCompression")
226
+
227
+ command_executed = shlex.join(cmd)
228
+ try:
229
+ result = subprocess.run(
230
+ cmd,
231
+ capture_output=True,
232
+ text=True,
233
+ check=True,
234
+ )
235
+ return {
236
+ "command_executed": command_executed,
237
+ "stdout": result.stdout,
238
+ "stderr": result.stderr,
239
+ "output_files": output_files,
240
+ }
241
+ except subprocess.CalledProcessError as e:
242
+ return {
243
+ "command_executed": command_executed,
244
+ "stdout": e.stdout,
245
+ "stderr": e.stderr,
246
+ "error": f"bamtools filter failed with exit code {e.returncode}",
247
+ "output_files": [],
248
+ }
249
+
250
+ @mcp.tool()
251
+ def header(in_bam: Path) -> dict:
252
+ """
253
+ Prints the header from a BAM file.
254
+
255
+ Args:
256
+ in_bam: The input BAM file.
257
+
258
+ Returns:
259
+ A dictionary containing the command executed, stdout (the header), and stderr.
260
+ """
261
+ if not in_bam.is_file():
262
+ raise FileNotFoundError(f"Input BAM file not found: {in_bam}")
263
+
264
+ cmd = ["bamtools", "header", "-in", str(in_bam)]
265
+ command_executed = shlex.join(cmd)
266
+ try:
267
+ result = subprocess.run(
268
+ cmd,
269
+ capture_output=True,
270
+ text=True,
271
+ check=True,
272
+ )
273
+ return {
274
+ "command_executed": command_executed,
275
+ "stdout": result.stdout,
276
+ "stderr": result.stderr,
277
+ "output_files": [],
278
+ }
279
+ except subprocess.CalledProcessError as e:
280
+ return {
281
+ "command_executed": command_executed,
282
+ "stdout": e.stdout,
283
+ "stderr": e.stderr,
284
+ "error": f"bamtools header failed with exit code {e.returncode}",
285
+ "output_files": [],
286
+ }
287
+
288
+ @mcp.tool()
289
+ def index(in_bam: Path) -> dict:
290
+ """
291
+ Generates an index for a BAM file. The index file (.bai) is created in the same directory.
292
+
293
+ Args:
294
+ in_bam: The input BAM file to index.
295
+
296
+ Returns:
297
+ A dictionary containing the command executed, stdout, stderr, and the path to the generated index file.
298
+ """
299
+ if not in_bam.is_file():
300
+ raise FileNotFoundError(f"Input BAM file not found: {in_bam}")
301
+
302
+ cmd = ["bamtools", "index", "-in", str(in_bam)]
303
+ output_index_file = in_bam.with_suffix(in_bam.suffix + '.bai')
304
+
305
+ command_executed = shlex.join(cmd)
306
+ try:
307
+ result = subprocess.run(
308
+ cmd,
309
+ capture_output=True,
310
+ text=True,
311
+ check=True,
312
+ )
313
+ if not output_index_file.is_file():
314
+ raise FileNotFoundError(f"Expected index file was not created: {output_index_file}")
315
+ return {
316
+ "command_executed": command_executed,
317
+ "stdout": result.stdout,
318
+ "stderr": result.stderr,
319
+ "output_files": [str(output_index_file)],
320
+ }
321
+ except subprocess.CalledProcessError as e:
322
+ return {
323
+ "command_executed": command_executed,
324
+ "stdout": e.stdout,
325
+ "stderr": e.stderr,
326
+ "error": f"bamtools index failed with exit code {e.returncode}",
327
+ "output_files": [],
328
+ }
329
+
330
+ @mcp.tool()
331
+ def merge(
332
+ in_bams: List[Path],
333
+ out_bam: Optional[Path] = None,
334
+ region: Optional[str] = None,
335
+ force: bool = False,
336
+ ) -> dict:
337
+ """
338
+ Merges multiple BAM files into a single file.
339
+
340
+ Args:
341
+ in_bams: A list of input BAM files to merge. Must contain at least two files.
342
+ out_bam: The output BAM file. If not provided, output is sent to stdout.
343
+ region: Merges only alignments that overlap this region.
344
+ force: Forces merge, even if headers are not identical. Uses the first BAM file's header.
345
+
346
+ Returns:
347
+ A dictionary containing the command executed, stdout, stderr, and output files.
348
+ """
349
+ if len(in_bams) < 2:
350
+ raise ValueError("At least two input BAM files must be provided for merging.")
351
+
352
+ cmd = ["bamtools", "merge"]
353
+ output_files = []
354
+
355
+ for bam_file in in_bams:
356
+ if not bam_file.is_file():
357
+ raise FileNotFoundError(f"Input BAM file not found: {bam_file}")
358
+ cmd.extend(["-in", str(bam_file)])
359
+
360
+ if out_bam:
361
+ cmd.extend(["-out", str(out_bam)])
362
+ output_files.append(str(out_bam))
363
+ if region:
364
+ cmd.extend(["-region", region])
365
+ if force:
366
+ cmd.append("-force")
367
+
368
+ command_executed = shlex.join(cmd)
369
+ try:
370
+ result = subprocess.run(
371
+ cmd,
372
+ capture_output=True,
373
+ text=True,
374
+ check=True,
375
+ )
376
+ return {
377
+ "command_executed": command_executed,
378
+ "stdout": result.stdout,
379
+ "stderr": result.stderr,
380
+ "output_files": output_files,
381
+ }
382
+ except subprocess.CalledProcessError as e:
383
+ return {
384
+ "command_executed": command_executed,
385
+ "stdout": e.stdout,
386
+ "stderr": e.stderr,
387
+ "error": f"bamtools merge failed with exit code {e.returncode}",
388
+ "output_files": [],
389
+ }
390
+
391
+ @mcp.tool()
392
+ def random(
393
+ in_bams: List[Path],
394
+ out_bam: Optional[Path] = None,
395
+ n: int = 100,
396
+ seed: int = 0,
397
+ ) -> dict:
398
+ """
399
+ Selects a random number of alignments from existing BAM file(s).
400
+
401
+ Args:
402
+ in_bams: The input BAM file(s).
403
+ out_bam: The output BAM file. If not provided, output is sent to stdout.
404
+ n: Number of alignments to select.
405
+ seed: Random seed.
406
+
407
+ Returns:
408
+ A dictionary containing the command executed, stdout, stderr, and output files.
409
+ """
410
+ if not in_bams:
411
+ raise ValueError("At least one input BAM file must be provided.")
412
+
413
+ cmd = ["bamtools", "random", "-n", str(n), "-seed", str(seed)]
414
+ output_files = []
415
+
416
+ for bam_file in in_bams:
417
+ if not bam_file.is_file():
418
+ raise FileNotFoundError(f"Input BAM file not found: {bam_file}")
419
+ cmd.extend(["-in", str(bam_file)])
420
+
421
+ if out_bam:
422
+ cmd.extend(["-out", str(out_bam)])
423
+ output_files.append(str(out_bam))
424
+
425
+ command_executed = shlex.join(cmd)
426
+ try:
427
+ result = subprocess.run(
428
+ cmd,
429
+ capture_output=True,
430
+ text=True,
431
+ check=True,
432
+ )
433
+ return {
434
+ "command_executed": command_executed,
435
+ "stdout": result.stdout,
436
+ "stderr": result.stderr,
437
+ "output_files": output_files,
438
+ }
439
+ except subprocess.CalledProcessError as e:
440
+ return {
441
+ "command_executed": command_executed,
442
+ "stdout": e.stdout,
443
+ "stderr": e.stderr,
444
+ "error": f"bamtools random failed with exit code {e.returncode}",
445
+ "output_files": [],
446
+ }
447
+
448
+ @mcp.tool()
449
+ def resolve(in_bam: Path, out_bam: Path) -> dict:
450
+ """
451
+ Resolves paired-end reads, marking the IsProperPair flag as needed.
452
+
453
+ Args:
454
+ in_bam: The input BAM file.
455
+ out_bam: The output BAM file.
456
+
457
+ Returns:
458
+ A dictionary containing the command executed, stdout, stderr, and the output file path.
459
+ """
460
+ if not in_bam.is_file():
461
+ raise FileNotFoundError(f"Input BAM file not found: {in_bam}")
462
+
463
+ cmd = ["bamtools", "resolve", "-in", str(in_bam), "-out", str(out_bam)]
464
+ command_executed = shlex.join(cmd)
465
+ try:
466
+ result = subprocess.run(
467
+ cmd,
468
+ capture_output=True,
469
+ text=True,
470
+ check=True,
471
+ )
472
+ return {
473
+ "command_executed": command_executed,
474
+ "stdout": result.stdout,
475
+ "stderr": result.stderr,
476
+ "output_files": [str(out_bam)],
477
+ }
478
+ except subprocess.CalledProcessError as e:
479
+ return {
480
+ "command_executed": command_executed,
481
+ "stdout": e.stdout,
482
+ "stderr": e.stderr,
483
+ "error": f"bamtools resolve failed with exit code {e.returncode}",
484
+ "output_files": [],
485
+ }
486
+
487
+ @mcp.tool()
488
+ def revert(in_bam: Path, out_bam: Path) -> dict:
489
+ """
490
+ Removes duplicate marks and restores original base qualities from a BAM file.
491
+
492
+ Args:
493
+ in_bam: The input BAM file.
494
+ out_bam: The output BAM file.
495
+
496
+ Returns:
497
+ A dictionary containing the command executed, stdout, stderr, and the output file path.
498
+ """
499
+ if not in_bam.is_file():
500
+ raise FileNotFoundError(f"Input BAM file not found: {in_bam}")
501
+
502
+ cmd = ["bamtools", "revert", "-in", str(in_bam), "-out", str(out_bam)]
503
+ command_executed = shlex.join(cmd)
504
+ try:
505
+ result = subprocess.run(
506
+ cmd,
507
+ capture_output=True,
508
+ text=True,
509
+ check=True,
510
+ )
511
+ return {
512
+ "command_executed": command_executed,
513
+ "stdout": result.stdout,
514
+ "stderr": result.stderr,
515
+ "output_files": [str(out_bam)],
516
+ }
517
+ except subprocess.CalledProcessError as e:
518
+ return {
519
+ "command_executed": command_executed,
520
+ "stdout": e.stdout,
521
+ "stderr": e.stderr,
522
+ "error": f"bamtools revert failed with exit code {e.returncode}",
523
+ "output_files": [],
524
+ }
525
+
526
+ @mcp.tool()
527
+ def sort(
528
+ in_bam: Path,
529
+ out_bam: Optional[Path] = None,
530
+ by: str = "position",
531
+ order: str = "ascending",
532
+ n: int = 1000,
533
+ mem: int = 512,
534
+ ) -> dict:
535
+ """
536
+ Sorts a BAM file by position or name.
537
+
538
+ Args:
539
+ in_bam: The input BAM file.
540
+ out_bam: The output BAM file. If not provided, output is sent to stdout.
541
+ by: Sort by 'position' or 'name'.
542
+ order: Sort in 'ascending' or 'descending' order.
543
+ n: Max number of alignments to buffer in memory.
544
+ mem: Max memory to use (in megabytes).
545
+
546
+ Returns:
547
+ A dictionary containing the command executed, stdout, stderr, and output files.
548
+ """
549
+ if not in_bam.is_file():
550
+ raise FileNotFoundError(f"Input BAM file not found: {in_bam}")
551
+ if by not in ["position", "name"]:
552
+ raise ValueError("Sort 'by' must be 'position' or 'name'.")
553
+ if order not in ["ascending", "descending"]:
554
+ raise ValueError("Sort 'order' must be 'ascending' or 'descending'.")
555
+
556
+ cmd = ["bamtools", "sort", "-in", str(in_bam), "-by", by, "-order", order, "-n", str(n), "-mem", str(mem)]
557
+ output_files = []
558
+
559
+ if out_bam:
560
+ cmd.extend(["-out", str(out_bam)])
561
+ output_files.append(str(out_bam))
562
+
563
+ command_executed = shlex.join(cmd)
564
+ try:
565
+ result = subprocess.run(
566
+ cmd,
567
+ capture_output=True,
568
+ text=True,
569
+ check=True,
570
+ )
571
+ return {
572
+ "command_executed": command_executed,
573
+ "stdout": result.stdout,
574
+ "stderr": result.stderr,
575
+ "output_files": output_files,
576
+ }
577
+ except subprocess.CalledProcessError as e:
578
+ return {
579
+ "command_executed": command_executed,
580
+ "stdout": e.stdout,
581
+ "stderr": e.stderr,
582
+ "error": f"bamtools sort failed with exit code {e.returncode}",
583
+ "output_files": [],
584
+ }
585
+
586
+ @mcp.tool()
587
+ def split(
588
+ in_bam: Path,
589
+ output_dir: Path,
590
+ stub: Optional[str] = None,
591
+ by: Optional[str] = None,
592
+ reference: bool = False,
593
+ mapped: bool = False,
594
+ paired: bool = False,
595
+ tag: Optional[str] = None,
596
+ ) -> dict:
597
+ """
598
+ Splits a BAM file based on a specified property, creating multiple output BAM files.
599
+
600
+ Args:
601
+ in_bam: The input BAM file.
602
+ output_dir: The directory where output files will be created.
603
+ stub: The prefix for output file names. Defaults to the input file name.
604
+ by: Split file by a specific property.
605
+ reference: Split by reference sequence.
606
+ mapped: Split into mapped and unmapped files.
607
+ paired: Split into paired and single-end files.
608
+ tag: Split by the value of a specific tag.
609
+
610
+ Returns:
611
+ A dictionary containing the command executed, stdout, stderr, and a list of created files.
612
+ """
613
+ if not in_bam.is_file():
614
+ raise FileNotFoundError(f"Input BAM file not found: {in_bam}")
615
+
616
+ split_options = [by is not None, reference, mapped, paired, tag is not None]
617
+ if sum(split_options) != 1:
618
+ raise ValueError("Exactly one split criterion (by, reference, mapped, paired, or tag) must be specified.")
619
+
620
+ output_dir.mkdir(parents=True, exist_ok=True)
621
+
622
+ cmd = ["bamtools", "split", "-in", str(in_bam)]
623
+
624
+ # Use the stub to direct output to the specified directory
625
+ output_stub = stub if stub else in_bam.stem
626
+ cmd.extend(["-stub", str(output_dir / output_stub)])
627
+
628
+ if by:
629
+ cmd.extend(["-by", by])
630
+ if reference:
631
+ cmd.append("-reference")
632
+ if mapped:
633
+ cmd.append("-mapped")
634
+ if paired:
635
+ cmd.append("-paired")
636
+ if tag:
637
+ cmd.extend(["-tag", tag])
638
+
639
+ command_executed = shlex.join(cmd)
640
+ try:
641
+ result = subprocess.run(
642
+ cmd,
643
+ capture_output=True,
644
+ text=True,
645
+ check=True,
646
+ )
647
+ # Find all files created in the output directory
648
+ created_files = [str(p) for p in output_dir.glob(f"{output_stub}*")]
649
+ return {
650
+ "command_executed": command_executed,
651
+ "stdout": result.stdout,
652
+ "stderr": result.stderr,
653
+ "output_files": created_files,
654
+ }
655
+ except subprocess.CalledProcessError as e:
656
+ return {
657
+ "command_executed": command_executed,
658
+ "stdout": e.stdout,
659
+ "stderr": e.stderr,
660
+ "error": f"bamtools split failed with exit code {e.returncode}",
661
+ "output_files": [],
662
+ }
663
+
664
+ @mcp.tool()
665
+ def stats(in_bams: List[Path], insert: bool = False) -> dict:
666
+ """
667
+ Prints basic statistics from one or more BAM files.
668
+
669
+ Args:
670
+ in_bams: A list of input BAM files.
671
+ insert: If True, print insert size summary.
672
+
673
+ Returns:
674
+ A dictionary containing the command executed, stdout (the stats), and stderr.
675
+ """
676
+ if not in_bams:
677
+ raise ValueError("At least one input BAM file must be provided.")
678
+
679
+ cmd = ["bamtools", "stats"]
680
+ for bam_file in in_bams:
681
+ if not bam_file.is_file():
682
+ raise FileNotFoundError(f"Input BAM file not found: {bam_file}")
683
+ cmd.extend(["-in", str(bam_file)])
684
+
685
+ if insert:
686
+ cmd.append("-insert")
687
+
688
+ command_executed = shlex.join(cmd)
689
+ try:
690
+ result = subprocess.run(
691
+ cmd,
692
+ capture_output=True,
693
+ text=True,
694
+ check=True,
695
+ )
696
+ return {
697
+ "command_executed": command_executed,
698
+ "stdout": result.stdout,
699
+ "stderr": result.stderr,
700
+ "output_files": [],
701
+ }
702
+ except subprocess.CalledProcessError as e:
703
+ return {
704
+ "command_executed": command_executed,
705
+ "stdout": e.stdout,
706
+ "stderr": e.stderr,
707
+ "error": f"bamtools stats failed with exit code {e.returncode}",
708
+ "output_files": [],
709
+ }
710
+
711
+ if __name__ == "__main__":
712
+ mcp.run(transport="stdio")
Biomni/mcp_generated/mcp_bamtools/app/bamtools_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_bamtools/app/bamtools_server.py')
11
+ LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name)
12
+ SERVER_NAME = 'biosci_bamtools'
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_bamtools/docker-compose.yml ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ version: '3.8'
2
+
3
+ services:
4
+ mcp-bamtools:
5
+ build: .
6
+ image: mcp-bamtools:latest
7
+ container_name: mcp-bamtools
8
+ ports:
9
+ - "8000:8000"
10
+ environment:
11
+ - MCP_SERVER_NAME=bamtools
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_bamtools/environment.yaml ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ name: mcp-tool
3
+ channels:
4
+ - bioconda
5
+ - conda-forge
6
+ - defaults
7
+ dependencies:
8
+ - bamtools
9
+ - python=3.10
10
+
Biomni/mcp_generated/mcp_bamtools/requirements.txt ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ fastmcp
2
+ mcp
Biomni/mcp_generated/mcp_bioconductor-alabaster.spatial/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-alabaster.spatial via conda (e.g., from bioconda)
14
+ RUN conda install -c bioconda bioconductor-alabaster.spatial -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-alabaster.spatial_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-alabaster.spatial_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-alabaster.spatial_server.py"]
40
+
Biomni/mcp_generated/mcp_bioconductor-alabaster.spatial/docker-compose.yml ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ version: '3.8'
2
+
3
+ services:
4
+ mcp-bioconductor-alabaster.spatial:
5
+ build: .
6
+ image: mcp-bioconductor-alabaster.spatial:latest
7
+ container_name: mcp-bioconductor-alabaster.spatial
8
+ ports:
9
+ - "8000:8000"
10
+ environment:
11
+ - MCP_SERVER_NAME=bioconductor-alabaster.spatial
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-alabaster.spatial/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-alabaster.spatial
9
+ - python=3.10
10
+
Biomni/mcp_generated/mcp_bioconductor-alabaster.spatial/requirements.txt ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ fastmcp
2
+ mcp
Biomni/mcp_generated/mcp_bioconductor-cardspa/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-cardspa via conda (e.g., from bioconda)
14
+ RUN conda install -c bioconda bioconductor-cardspa -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-cardspa_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-cardspa_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-cardspa_server.py"]
40
+
Biomni/mcp_generated/mcp_bioconductor-cardspa/app/bioconductor-cardspa_server.py ADDED
@@ -0,0 +1,189 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import subprocess
2
+ import tempfile
3
+ from pathlib import Path
4
+ from typing import Dict, List, Literal, Union
5
+
6
+ # This is a placeholder for the MCP framework's decorator.
7
+ # The user's environment will provide the actual mcp.tool implementation.
8
+ class Mcp:
9
+ def tool(self):
10
+ def decorator(f):
11
+ return f
12
+ return decorator
13
+ mcp = Mcp()
14
+
15
+
16
+ from mcp.server.fastmcp import FastMCP
17
+
18
+ SERVER_NAME = 'local_bioconductor_cardspa'
19
+ mcp = FastMCP(SERVER_NAME)
20
+
21
+ @mcp.tool()
22
+ def cardspa(
23
+ sce_object: Path,
24
+ output_path: Path,
25
+ phenotype_col: str,
26
+ location_col_1: str = "row",
27
+ location_col_2: str = "col",
28
+ method: Literal["Giotto", "SpaGCN", "BayesSpace"] = "Giotto",
29
+ num_results: int = 10,
30
+ num_core: int = 1,
31
+ min_cells: int = 10,
32
+ min_pairs: int = 10,
33
+ ) -> Dict[str, Union[str, List[str], int]]:
34
+ """
35
+ Performs spatial analysis on single-cell data using the cardspa R package.
36
+
37
+ This tool wraps the main `cardspa` function, which can utilize different
38
+ backend methods like Giotto, SpaGCN, or BayesSpace to identify significant
39
+ co-enrichment of cell types in spatial transcriptomics data.
40
+
41
+ Args:
42
+ sce_object: Path to the input SingleCellExperiment object in RDS format.
43
+ output_path: Path to save the output results object in RDS format.
44
+ phenotype_col: The column name in the SCE object's colData that contains cell type annotations.
45
+ location_col_1: The column name for the first spatial coordinate (e.g., 'row', 'x').
46
+ location_col_2: The column name for the second spatial coordinate (e.g., 'col', 'y').
47
+ method: The computational method to use for the analysis.
48
+ num_results: The number of top co-enriched cell type pairs to return.
49
+ num_core: The number of CPU cores to use for parallel processing.
50
+ min_cells: The minimum number of cells required for a cell type to be included in the analysis.
51
+ min_pairs: The minimum number of interacting pairs for a result to be considered significant.
52
+
53
+ Returns:
54
+ A dictionary containing the executed command, stdout, stderr, and a list of output files.
55
+ In case of an error, it includes an error message and return code.
56
+ """
57
+ # --- Input Validation ---
58
+ if not sce_object.is_file():
59
+ raise FileNotFoundError(f"Input file not found: {sce_object}")
60
+
61
+ if not output_path.parent.is_dir():
62
+ try:
63
+ output_path.parent.mkdir(parents=True, exist_ok=True)
64
+ except OSError as e:
65
+ raise NotADirectoryError(
66
+ f"Output directory does not exist and could not be created: {output_path.parent}"
67
+ ) from e
68
+
69
+ if num_results <= 0:
70
+ raise ValueError("num_results must be a positive integer.")
71
+ if num_core <= 0:
72
+ raise ValueError("num_core must be a positive integer.")
73
+ if min_cells <= 0:
74
+ raise ValueError("min_cells must be a positive integer.")
75
+ if min_pairs <= 0:
76
+ raise ValueError("min_pairs must be a positive integer.")
77
+
78
+ # R script to be executed. It includes package installation for portability.
79
+ r_script_content = """
80
+ # run_cardspa.R
81
+ options(warn=-1)
82
+ suppressPackageStartupMessages({
83
+ if (!requireNamespace("argparser", quietly = TRUE)) {
84
+ install.packages("argparser", repos = "http://cran.us.r-project.org", quiet = TRUE)
85
+ }
86
+ if (!requireNamespace("BiocManager", quietly = TRUE)) {
87
+ install.packages("BiocManager", repos = "http://cran.us.r-project.org", quiet = TRUE)
88
+ }
89
+ if (!requireNamespace("cardspa", quietly = TRUE)) {
90
+ BiocManager::install("cardspa", update=FALSE, ask=FALSE, quiet = TRUE)
91
+ }
92
+ if (!requireNamespace("SingleCellExperiment", quietly = TRUE)) {
93
+ BiocManager::install("SingleCellExperiment", update=FALSE, ask=FALSE, quiet = TRUE)
94
+ }
95
+ library(argparser)
96
+ library(cardspa)
97
+ library(SingleCellExperiment)
98
+ })
99
+
100
+ p <- arg_parser("Run cardspa analysis")
101
+ p <- add_argument(p, "--sce_object", help="Path to the input SingleCellExperiment RDS file", type="character")
102
+ p <- add_argument(p, "--output_path", help="Path to save the output RDS file", type="character")
103
+ p <- add_argument(p, "--phenotype_col", help="Column name for cell types", type="character")
104
+ p <- add_argument(p, "--location_col_1", help="First column name for spatial coordinates", default="row", type="character")
105
+ p <- add_argument(p, "--location_col_2", help="Second column name for spatial coordinates", default="col", type="character")
106
+ p <- add_argument(p, "--method", help="Method for spatial analysis (Giotto, SpaGCN, BayesSpace)", default="Giotto", type="character")
107
+ p <- add_argument(p, "--num_results", help="Number of top results to return", default=10, type="integer")
108
+ p <- add_argument(p, "--num_core", help="Number of cores for parallel processing", default=1, type="integer")
109
+ p <- add_argument(p, "--min_cells", help="Minimum number of cells for a cell type", default=10, type="integer")
110
+ p <- add_argument(p, "--min_pairs", help="Minimum number of interacting pairs", default=10, type="integer")
111
+
112
+ argv <- parse_args(p)
113
+
114
+ if (is.na(argv$sce_object) || is.na(argv$output_path) || is.na(argv$phenotype_col)) {
115
+ stop("Missing required arguments: --sce_object, --output_path, or --phenotype_col", call. = FALSE)
116
+ }
117
+
118
+ cat("Reading input SCE object from:", argv$sce_object, "\\n")
119
+ sce <- readRDS(argv$sce_object)
120
+
121
+ cat("Running cardspa analysis with method:", argv$method, "\\n")
122
+ results <- cardspa(
123
+ sce_object = sce,
124
+ location_col = c(argv$location_col_1, argv$location_col_2),
125
+ phenotype_col = argv$phenotype_col,
126
+ method = argv$method,
127
+ num_results = argv$num_results,
128
+ num_core = argv$num_core,
129
+ min_cells = argv$min_cells,
130
+ min_pairs = argv$min_pairs
131
+ )
132
+
133
+ cat("Saving results to:", argv$output_path, "\\n")
134
+ saveRDS(results, file = argv$output_path)
135
+ cat("cardspa analysis complete. Results saved to:", argv$output_path, "\\n")
136
+ """
137
+
138
+ # --- Subprocess Execution ---
139
+ with tempfile.NamedTemporaryFile(mode='w', suffix=".R", delete=False) as r_script_file:
140
+ r_script_file.write(r_script_content)
141
+ r_script_path = Path(r_script_file.name)
142
+
143
+ cmd = [
144
+ "Rscript", str(r_script_path),
145
+ "--sce_object", str(sce_object.resolve()),
146
+ "--output_path", str(output_path.resolve()),
147
+ "--phenotype_col", phenotype_col,
148
+ "--location_col_1", location_col_1,
149
+ "--location_col_2", location_col_2,
150
+ "--method", method,
151
+ "--num_results", str(num_results),
152
+ "--num_core", str(num_core),
153
+ "--min_cells", str(min_cells),
154
+ "--min_pairs", str(min_pairs),
155
+ ]
156
+ command_executed = " ".join(cmd)
157
+
158
+ try:
159
+ result = subprocess.run(
160
+ cmd,
161
+ check=True,
162
+ capture_output=True,
163
+ text=True,
164
+ encoding='utf-8'
165
+ )
166
+ return {
167
+ "command_executed": command_executed,
168
+ "stdout": result.stdout,
169
+ "stderr": result.stderr,
170
+ "output_files": [str(output_path.resolve())]
171
+ }
172
+ except FileNotFoundError:
173
+ raise RuntimeError("Rscript command not found. Please ensure R is installed and in your system's PATH.")
174
+ except subprocess.CalledProcessError as e:
175
+ return {
176
+ "command_executed": command_executed,
177
+ "stdout": e.stdout,
178
+ "stderr": e.stderr,
179
+ "error": "cardspa execution failed.",
180
+ "return_code": e.returncode,
181
+ "output_files": []
182
+ }
183
+ finally:
184
+ # Ensure temporary script is always removed
185
+ if r_script_path.exists():
186
+ r_script_path.unlink()
187
+
188
+ if __name__ == "__main__":
189
+ mcp.run(transport="stdio")
Biomni/mcp_generated/mcp_bioconductor-cardspa/app/bioconductor-cardspa_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-cardspa/app/bioconductor-cardspa_server.py')
11
+ LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name)
12
+ SERVER_NAME = 'biosci_bioconductor_cardspa'
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-cardspa/app/requirements.txt ADDED
@@ -0,0 +1 @@
 
 
1
+
Biomni/mcp_generated/mcp_bioconductor-cardspa/docker-compose.yml ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ version: '3.8'
2
+
3
+ services:
4
+ mcp-bioconductor-cardspa:
5
+ build: .
6
+ image: mcp-bioconductor-cardspa:latest
7
+ container_name: mcp-bioconductor-cardspa
8
+ ports:
9
+ - "8000:8000"
10
+ environment:
11
+ - MCP_SERVER_NAME=bioconductor-cardspa
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-cardspa/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-cardspa
9
+ - python=3.10
10
+
Biomni/mcp_generated/mcp_bioconductor-cardspa/requirements.txt ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ fastmcp
2
+ mcp
Biomni/mcp_generated/mcp_bioconductor-cellid/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-cellid via conda (e.g., from bioconda)
14
+ RUN conda install -c bioconda bioconductor-cellid -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-cellid_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-cellid_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-cellid_server.py"]
40
+
Biomni/mcp_generated/mcp_bioconductor-cellid/app/bioconductor-cellid_server.py ADDED
@@ -0,0 +1,288 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import subprocess
2
+ import os
3
+ from pathlib import Path
4
+ from typing import Optional, List, Union
5
+ import tempfile
6
+
7
+ def run_r_command(script_content: str):
8
+ """Helper function to execute R code via Rscript"""
9
+ with tempfile.NamedTemporaryFile(mode='w', suffix='.R', delete=False) as tmp:
10
+ tmp.write(script_content)
11
+ tmp_path = tmp.name
12
+
13
+ try:
14
+ result = subprocess.run(
15
+ ["Rscript", tmp_path],
16
+ capture_output=True,
17
+ text=True,
18
+ check=True
19
+ )
20
+ return result.stdout, result.stderr
21
+ except subprocess.CalledProcessError as e:
22
+ raise RuntimeError(f"R execution failed: {e.stderr}\nStdout: {e.stdout}")
23
+ finally:
24
+ if os.path.exists(tmp_path):
25
+ os.remove(tmp_path)
26
+
27
+ from mcp.server.fastmcp import FastMCP
28
+
29
+ SERVER_NAME = 'local_bioconductor_cellid'
30
+ mcp = FastMCP(SERVER_NAME)
31
+
32
+ @mcp.tool()
33
+ def cellid_run_mca(
34
+ input_rds: str,
35
+ output_rds: str,
36
+ nmcs: int = 50,
37
+ assay: str = "RNA",
38
+ slot: str = "counts",
39
+ features: Optional[List[str]] = None
40
+ ):
41
+ """
42
+ Performs Multiple Correspondence Analysis (MCA) on a SingleCellExperiment or Seurat object.
43
+ MCA allows for the simultaneous representation of cells and genes in a common low-dimensional space.
44
+ """
45
+ input_path = Path(input_rds)
46
+ output_path = Path(output_rds)
47
+
48
+ if not input_path.exists():
49
+ return {"error": f"Input file {input_rds} not found."}
50
+
51
+ if nmcs <= 0:
52
+ return {"error": "nmcs must be a positive integer."}
53
+
54
+ features_r = "NULL"
55
+ if features:
56
+ features_formatted = ", ".join([f"'{f}'" for f in features])
57
+ features_r = f"c({features_formatted})"
58
+
59
+ r_script = f"""
60
+ library(CelliD)
61
+ library(Seurat)
62
+ library(SingleCellExperiment)
63
+
64
+ obj <- readRDS("{input_path}")
65
+
66
+ # Run MCA
67
+ obj <- RunMCA(obj, nmcs = {nmcs}, assay = "{assay}", slot = "{slot}", features = {features_r})
68
+
69
+ saveRDS(obj, "{output_path}")
70
+ """
71
+
72
+ try:
73
+ stdout, stderr = run_r_command(r_script)
74
+ return {
75
+ "command_executed": "RunMCA",
76
+ "stdout": stdout,
77
+ "stderr": stderr,
78
+ "output_files": [str(output_path)]
79
+ }
80
+ except Exception as e:
81
+ return {"error": str(e)}
82
+
83
+ @mcp.tool()
84
+ def cellid_get_cell_gene_set(
85
+ input_rds: str,
86
+ output_csv: str,
87
+ dims: int = 50,
88
+ n_features: int = 200
89
+ ):
90
+ """
91
+ Extracts the top gene signatures for each individual cell based on MCA coordinates.
92
+ """
93
+ input_path = Path(input_rds)
94
+ output_path = Path(output_csv)
95
+
96
+ if not input_path.exists():
97
+ return {"error": f"Input file {input_rds} not found."}
98
+
99
+ if dims <= 0 or n_features <= 0:
100
+ return {"error": "dims and n_features must be positive integers."}
101
+
102
+ r_script = f"""
103
+ library(CelliD)
104
+ library(Seurat)
105
+ library(SingleCellExperiment)
106
+ library(data.table)
107
+
108
+ obj <- readRDS("{input_path}")
109
+
110
+ # Extract gene sets per cell
111
+ # dims is treated as 1:dims
112
+ gene_sets <- GetCellGeneSet(obj, dims = 1:{dims}, n.features = {n_features})
113
+
114
+ # Convert list to a data table for export
115
+ # Each column is a cell, rows are top genes
116
+ max_len <- max(sapply(gene_sets, length))
117
+ dt <- as.data.frame(lapply(gene_sets, function(x) c(x, rep(NA, max_len - length(x)))))
118
+
119
+ write.csv(dt, "{output_path}", row.names = FALSE)
120
+ """
121
+
122
+ try:
123
+ stdout, stderr = run_r_command(r_script)
124
+ return {
125
+ "command_executed": "GetCellGeneSet",
126
+ "stdout": stdout,
127
+ "stderr": stderr,
128
+ "output_files": [str(output_path)]
129
+ }
130
+ except Exception as e:
131
+ return {"error": str(e)}
132
+
133
+ @mcp.tool()
134
+ def cellid_run_cell_hgt(
135
+ input_rds: str,
136
+ pathways_gmt: str,
137
+ output_rds: str,
138
+ dims: int = 50,
139
+ n_features: int = 200,
140
+ min_size: int = 5
141
+ ):
142
+ """
143
+ Performs cell-type assignment or functional enrichment using a Hypergeometric Test (HGT).
144
+ Matches per-cell gene signatures against a reference database (GMT format).
145
+ """
146
+ input_path = Path(input_rds)
147
+ gmt_path = Path(pathways_gmt)
148
+ output_path = Path(output_rds)
149
+
150
+ if not input_path.exists():
151
+ return {"error": f"Input RDS {input_rds} not found."}
152
+ if not gmt_path.exists():
153
+ return {"error": f"GMT file {pathways_gmt} not found."}
154
+
155
+ r_script = f"""
156
+ library(CelliD)
157
+ library(Seurat)
158
+ library(SingleCellExperiment)
159
+ library(fgsea)
160
+
161
+ obj <- readRDS("{input_path}")
162
+ pathways <- gmtPathways("{gmt_path}")
163
+
164
+ # Run Hypergeometric Test
165
+ # Returns a matrix of -log10(p-value)
166
+ hgt_matrix <- RunCellHGT(obj, pathways = pathways, dims = 1:{dims}, n.features = {n_features}, minSize = {min_size})
167
+
168
+ # If Seurat, we can add it as an assay or metadata
169
+ if(inherits(obj, "Seurat")) {{
170
+ obj[["HGT"]] <- CreateAssayObject(counts = hgt_matrix)
171
+ }} else {{
172
+ # For SCE, add to altExp
173
+ altExp(obj, "HGT") <- SummarizedExperiment(assays = list(counts = hgt_matrix))
174
+ }}
175
+
176
+ saveRDS(obj, "{output_path}")
177
+ """
178
+
179
+ try:
180
+ stdout, stderr = run_r_command(r_script)
181
+ return {
182
+ "command_executed": "RunCellHGT",
183
+ "stdout": stdout,
184
+ "stderr": stderr,
185
+ "output_files": [str(output_path)]
186
+ }
187
+ except Exception as e:
188
+ return {"error": str(e)}
189
+
190
+ @mcp.tool()
191
+ def cellid_run_gsea(
192
+ input_rds: str,
193
+ pathways_gmt: str,
194
+ output_rds: str,
195
+ dims: int = 50
196
+ ):
197
+ """
198
+ Performs Gene Set Enrichment Analysis (GSEA) on cells using the MCA gene rankings.
199
+ """
200
+ input_path = Path(input_rds)
201
+ gmt_path = Path(pathways_gmt)
202
+ output_path = Path(output_rds)
203
+
204
+ if not input_path.exists():
205
+ return {"error": f"Input RDS {input_rds} not found."}
206
+ if not gmt_path.exists():
207
+ return {"error": f"GMT file {pathways_gmt} not found."}
208
+
209
+ r_script = f"""
210
+ library(CelliD)
211
+ library(Seurat)
212
+ library(SingleCellExperiment)
213
+ library(fgsea)
214
+
215
+ obj <- readRDS("{input_path}")
216
+ pathways <- gmtPathways("{gmt_path}")
217
+
218
+ # Run GSEA
219
+ # This calculates enrichment scores for each cell and each pathway
220
+ gsea_res <- RunGSEA(obj, pathways = pathways, dims = 1:{dims})
221
+
222
+ # Save results (usually returns a matrix of NES)
223
+ if(inherits(obj, "Seurat")) {{
224
+ obj[["GSEA"]] <- CreateAssayObject(counts = gsea_res)
225
+ }} else {{
226
+ altExp(obj, "GSEA") <- SummarizedExperiment(assays = list(counts = gsea_res))
227
+ }}
228
+
229
+ saveRDS(obj, "{output_path}")
230
+ """
231
+
232
+ try:
233
+ stdout, stderr = run_r_command(r_script)
234
+ return {
235
+ "command_executed": "RunGSEA",
236
+ "stdout": stdout,
237
+ "stderr": stderr,
238
+ "output_files": [str(output_path)]
239
+ }
240
+ except Exception as e:
241
+ return {"error": str(e)}
242
+
243
+ @mcp.tool()
244
+ def cellid_run_group_mca(
245
+ input_rds: str,
246
+ group_by: str,
247
+ output_rds: str,
248
+ nmcs: int = 50,
249
+ assay: str = "RNA",
250
+ slot: str = "counts"
251
+ ):
252
+ """
253
+ Performs MCA on grouped cells (e.g., by cluster or cell type) rather than individual cells.
254
+ Useful for extracting group-specific gene signatures.
255
+ """
256
+ input_path = Path(input_rds)
257
+ output_path = Path(output_rds)
258
+
259
+ if not input_path.exists():
260
+ return {"error": f"Input file {input_rds} not found."}
261
+
262
+ r_script = f"""
263
+ library(CelliD)
264
+ library(Seurat)
265
+ library(SingleCellExperiment)
266
+
267
+ obj <- readRDS("{input_path}")
268
+
269
+ # Run Group MCA
270
+ # This aggregates cells by the specified metadata column before MCA
271
+ group_mca <- RunGroupMCA(obj, group.by = "{group_by}", nmcs = {nmcs}, assay = "{assay}", slot = "{slot}")
272
+
273
+ saveRDS(group_mca, "{output_path}")
274
+ """
275
+
276
+ try:
277
+ stdout, stderr = run_r_command(r_script)
278
+ return {
279
+ "command_executed": "RunGroupMCA",
280
+ "stdout": stdout,
281
+ "stderr": stderr,
282
+ "output_files": [str(output_path)]
283
+ }
284
+ except Exception as e:
285
+ return {"error": str(e)}
286
+
287
+ if __name__ == "__main__":
288
+ mcp.run(transport="stdio")
Biomni/mcp_generated/mcp_bioconductor-cellid/app/bioconductor-cellid_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-cellid/app/bioconductor-cellid_server.py')
11
+ LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name)
12
+ SERVER_NAME = 'biosci_bioconductor_cellid'
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-cellid/docker-compose.yml ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ version: '3.8'
2
+
3
+ services:
4
+ mcp-bioconductor-cellid:
5
+ build: .
6
+ image: mcp-bioconductor-cellid:latest
7
+ container_name: mcp-bioconductor-cellid
8
+ ports:
9
+ - "8000:8000"
10
+ environment:
11
+ - MCP_SERVER_NAME=bioconductor-cellid
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-cellid/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-cellid
9
+ - python=3.10
10
+
Biomni/mcp_generated/mcp_bioconductor-cellid/requirements.txt ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ fastmcp
2
+ mcp
Biomni/mcp_generated/mcp_bioconductor-ggspavis/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-ggspavis via conda (e.g., from bioconda)
14
+ RUN conda install -c bioconda bioconductor-ggspavis -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-ggspavis_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-ggspavis_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-ggspavis_server.py"]
40
+
Biomni/mcp_generated/mcp_bioconductor-ggspavis/app/bioconductor-ggspavis_server.py ADDED
@@ -0,0 +1,403 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import subprocess
2
+ import tempfile
3
+ import json
4
+ import shlex
5
+ from pathlib import Path
6
+ from typing import Optional, List, Literal
7
+
8
+ # This R script acts as a generic command-line wrapper for ggspavis functions.
9
+ # It is written to a temporary file and executed by the Python functions below.
10
+ # It dynamically installs required R packages, making the tool self-contained.
11
+ R_SCRIPT = """
12
+ # ggspavis_runner.R
13
+ # A generic runner for ggspavis functions
14
+
15
+ # Suppress startup messages for cleaner output
16
+ suppressPackageStartupMessages({
17
+ # Check if packages are installed, install if not
18
+ if (!requireNamespace("argparse", quietly = TRUE)) install.packages("argparse", repos = "http://cran.us.r-project.org")
19
+ if (!requireNamespace("jsonlite", quietly = TRUE)) install.packages("jsonlite", repos = "http://cran.us.r-project.org")
20
+ if (!requireNamespace("ggplot2", quietly = TRUE)) install.packages("ggplot2", repos = "http://cran.us.r-project.org")
21
+ if (!requireNamespace("BiocManager", quietly = TRUE)) install.packages("BiocManager", repos = "http://cran.us.r-project.org")
22
+
23
+ # Use BiocManager to install Bioconductor packages
24
+ if (!requireNamespace("SpatialExperiment", quietly = TRUE)) BiocManager::install("SpatialExperiment", update = FALSE, ask = FALSE)
25
+ if (!requireNamespace("ggspavis", quietly = TRUE)) BiocManager::install("ggspavis", update = FALSE, ask = FALSE)
26
+ })
27
+
28
+ library(argparse)
29
+ library(jsonlite)
30
+ library(ggplot2)
31
+ library(SpatialExperiment)
32
+ library(ggspavis)
33
+
34
+ # Main function to parse arguments and call the appropriate ggspavis function
35
+ main <- function() {
36
+ parser <- ArgumentParser(description = "A command-line wrapper for ggspavis functions.")
37
+
38
+ parser$add_argument("func", help = "The ggspavis function to call (e.g., plotSpots, plotFeature).")
39
+ parser$add_argument("--spe_path", required = TRUE, help = "Path to the SpatialExperiment object (.rds file).")
40
+ parser$add_argument("--output_path", required = TRUE, help = "Path to save the output plot.")
41
+ parser$add_argument("--width", type = "double", default = 7, help = "Width of the output plot in inches.")
42
+ parser$add_argument("--height", type = "double", default = 7, help = "Height of the output plot in inches.")
43
+ parser$add_argument("--json_args", type = "character", default = "{}", help = "A JSON string of arguments for the ggspavis function.")
44
+
45
+ args <- parser$parse_args()
46
+
47
+ # Load SpatialExperiment object
48
+ if (!file.exists(args$spe_path)) {
49
+ stop("Input SPE file not found: ", args$spe_path)
50
+ }
51
+ spe <- readRDS(args$spe_path)
52
+
53
+ # Parse JSON arguments for the target function
54
+ func_args <- fromJSON(args$json_args)
55
+
56
+ # Add the main 'spe' object to the arguments list
57
+ func_args <- c(list(spe = spe), func_args)
58
+
59
+ # Check if the requested function exists in the ggspavis package
60
+ if (!exists(args$func, where = "package:ggspavis", mode = "function")) {
61
+ stop("Function '", args$func, "' not found in ggspavis package.")
62
+ }
63
+
64
+ # Dynamically call the ggspavis function with the parsed arguments
65
+ plot_obj <- do.call(args$func, func_args)
66
+
67
+ # Save the generated plot to the specified output file
68
+ ggsave(
69
+ filename = args$output_path,
70
+ plot = plot_obj,
71
+ width = args$width,
72
+ height = args$height,
73
+ dpi = 300
74
+ )
75
+
76
+ cat("Plot successfully saved to", args$output_path, "\\n")
77
+ }
78
+
79
+ # Execute the main function and handle potential errors
80
+ tryCatch({
81
+ main()
82
+ }, error = function(e) {
83
+ message("Error: ", e$message)
84
+ quit(status = 1)
85
+ })
86
+ """
87
+
88
+ def _run_ggspavis(
89
+ function_name: str,
90
+ spe_path: Path,
91
+ output_path: Path,
92
+ plot_width: float,
93
+ plot_height: float,
94
+ json_args: dict,
95
+ ) -> dict:
96
+ """
97
+ Helper function to execute the ggspavis R script wrapper.
98
+
99
+ Args:
100
+ function_name: The ggspavis function to execute (e.g., 'plotSpots').
101
+ spe_path: Path to the input SpatialExperiment RDS file.
102
+ output_path: Path to save the output plot image.
103
+ plot_width: Width of the output plot in inches.
104
+ plot_height: Height of the output plot in inches.
105
+ json_args: A dictionary of arguments for the ggspavis function.
106
+
107
+ Returns:
108
+ A dictionary containing the command executed, stdout, stderr, and output file path.
109
+ """
110
+ # Ensure output directory exists
111
+ output_path.parent.mkdir(parents=True, exist_ok=True)
112
+
113
+ # Use a temporary file to store and execute the R script
114
+ with tempfile.NamedTemporaryFile(mode='w', suffix=".R", delete=False) as r_script_file:
115
+ r_script_path = Path(r_script_file.name)
116
+ r_script_file.write(R_SCRIPT)
117
+
118
+ command = [
119
+ "Rscript",
120
+ str(r_script_path),
121
+ function_name,
122
+ "--spe_path", str(spe_path),
123
+ "--output_path", str(output_path),
124
+ "--width", str(plot_width),
125
+ "--height", str(plot_height),
126
+ "--json_args", json.dumps(json_args),
127
+ ]
128
+
129
+ command_executed = " ".join(shlex.quote(c) for c in command)
130
+
131
+ try:
132
+ process = subprocess.run(
133
+ command,
134
+ capture_output=True,
135
+ text=True,
136
+ check=True,
137
+ )
138
+ stdout = process.stdout
139
+ stderr = process.stderr
140
+ output_files = {"plot": str(output_path)} if output_path.exists() else {}
141
+
142
+ except FileNotFoundError:
143
+ # This error occurs if 'Rscript' is not in the system's PATH
144
+ return {
145
+ "command_executed": command_executed,
146
+ "stdout": "",
147
+ "stderr": "Error: 'Rscript' command not found. Please ensure R is installed and in your system's PATH.",
148
+ "output_files": {},
149
+ }
150
+ except subprocess.CalledProcessError as e:
151
+ # This error occurs if the R script returns a non-zero exit code
152
+ return {
153
+ "command_executed": command_executed,
154
+ "stdout": e.stdout,
155
+ "stderr": e.stderr,
156
+ "output_files": {},
157
+ }
158
+ finally:
159
+ # Clean up the temporary R script file
160
+ r_script_path.unlink()
161
+
162
+ return {
163
+ "command_executed": command_executed,
164
+ "stdout": stdout,
165
+ "stderr": stderr,
166
+ "output_files": output_files,
167
+ }
168
+
169
+
170
+ from mcp.server.fastmcp import FastMCP
171
+
172
+ SERVER_NAME = 'local_bioconductor_ggspavis'
173
+ mcp = FastMCP(SERVER_NAME)
174
+
175
+ @mcp.tool()
176
+ def ggspavis_plot_spots(
177
+ spe_path: Path,
178
+ output_filename: str,
179
+ fill: Optional[str] = None,
180
+ highlight: Optional[str] = None,
181
+ facet_by: Optional[str] = None,
182
+ assay: str = "logcounts",
183
+ palette: str = "viridis",
184
+ point_size: float = 1.5,
185
+ point_alpha: float = 1.0,
186
+ plot_width: float = 7.0,
187
+ plot_height: float = 7.0,
188
+ ):
189
+ """
190
+ Generates a spatial plot of spots/capture locations using ggspavis::plotSpots.
191
+
192
+ Args:
193
+ spe_path: Path to the input SpatialExperiment object (.rds file).
194
+ output_filename: Name of the output plot file (e.g., 'plot.png').
195
+ fill: Column name in colData or a feature name to use for spot fill color.
196
+ highlight: Column name in colData to use for highlighting spots.
197
+ facet_by: Column name in colData to use for faceting the plot.
198
+ assay: Name of the assay to use if 'fill' is a feature name.
199
+ palette: Color palette for continuous values (e.g., 'viridis', 'magma').
200
+ point_size: Size of the points representing spots.
201
+ point_alpha: Alpha transparency of the points.
202
+ plot_width: Width of the output plot in inches.
203
+ plot_height: Height of the output plot in inches.
204
+ """
205
+ # Input validation
206
+ if not spe_path.is_file():
207
+ raise FileNotFoundError(f"Input file not found: {spe_path}")
208
+ if point_size <= 0:
209
+ raise ValueError("point_size must be positive.")
210
+ if not (0.0 <= point_alpha <= 1.0):
211
+ raise ValueError("point_alpha must be between 0.0 and 1.0.")
212
+
213
+ # Prepare arguments for the R function
214
+ json_args = {
215
+ "fill": fill,
216
+ "highlight": highlight,
217
+ "facet_by": facet_by,
218
+ "assay": assay,
219
+ "palette": palette,
220
+ "point_size": point_size,
221
+ "point_alpha": point_alpha,
222
+ }
223
+ # Filter out None values so they are not passed in the JSON
224
+ json_args = {k: v for k, v in json_args.items() if v is not None}
225
+
226
+ output_path = Path(output_filename)
227
+
228
+ return _run_ggspavis(
229
+ function_name="plotSpots",
230
+ spe_path=spe_path,
231
+ output_path=output_path,
232
+ plot_width=plot_width,
233
+ plot_height=plot_height,
234
+ json_args=json_args,
235
+ )
236
+
237
+
238
+ @mcp.tool()
239
+ def ggspavis_plot_feature(
240
+ spe_path: Path,
241
+ output_filename: str,
242
+ feature: str,
243
+ assay: str = "logcounts",
244
+ platform: Literal["Visium", "ST", "VisiumHD", "mIF", "seqFISH", "MERFISH", "Xenium"] = "Visium",
245
+ palette: str = "viridis",
246
+ point_size: float = 1.5,
247
+ plot_width: float = 7.0,
248
+ plot_height: float = 7.0,
249
+ ):
250
+ """
251
+ Visualizes the expression of a single feature (e.g., gene) in spatial context using ggspavis::plotFeature.
252
+
253
+ Args:
254
+ spe_path: Path to the input SpatialExperiment object (.rds file).
255
+ output_filename: Name of the output plot file (e.g., 'feature_plot.png').
256
+ feature: Name of the feature (gene) to plot from the specified assay.
257
+ assay: Name of the assay containing the feature.
258
+ platform: The spatial transcriptomics platform.
259
+ palette: Color palette for continuous expression values.
260
+ point_size: Size of the points.
261
+ plot_width: Width of the output plot in inches.
262
+ plot_height: Height of the output plot in inches.
263
+ """
264
+ # Input validation
265
+ if not spe_path.is_file():
266
+ raise FileNotFoundError(f"Input file not found: {spe_path}")
267
+ if not feature:
268
+ raise ValueError("A feature name must be provided.")
269
+
270
+ # Prepare arguments for the R function
271
+ json_args = {
272
+ "feature": feature,
273
+ "assay": assay,
274
+ "platform": platform,
275
+ "palette": palette,
276
+ "point_size": point_size,
277
+ }
278
+ json_args = {k: v for k, v in json_args.items() if v is not None}
279
+
280
+ output_path = Path(output_filename)
281
+
282
+ return _run_ggspavis(
283
+ function_name="plotFeature",
284
+ spe_path=spe_path,
285
+ output_path=output_path,
286
+ plot_width=plot_width,
287
+ plot_height=plot_height,
288
+ json_args=json_args,
289
+ )
290
+
291
+
292
+ @mcp.tool()
293
+ def ggspavis_plot_dim_red(
294
+ spe_path: Path,
295
+ output_filename: str,
296
+ reduction_type: str,
297
+ x_axis: Optional[str] = None,
298
+ y_axis: Optional[str] = None,
299
+ color_by: Optional[str] = None,
300
+ shape_by: Optional[str] = None,
301
+ assay: str = "logcounts",
302
+ point_size: float = 1.5,
303
+ plot_width: float = 7.0,
304
+ plot_height: float = 7.0,
305
+ ):
306
+ """
307
+ Plots dimensionality reduction results (e.g., UMAP, PCA) using ggspavis::plotDimRed.
308
+
309
+ Args:
310
+ spe_path: Path to the input SpatialExperiment object (.rds file).
311
+ output_filename: Name of the output plot file (e.g., 'umap.png').
312
+ reduction_type: Name of the dimensionality reduction result in reducedDims(spe).
313
+ x_axis: Column name for the x-axis (e.g., 'UMAP_1'). Defaults to type_1.
314
+ y_axis: Column name for the y-axis (e.g., 'UMAP_2'). Defaults to type_2.
315
+ color_by: Column name in colData or feature name to use for point color.
316
+ shape_by: Column name in colData to use for point shape.
317
+ assay: Name of assay to use if 'color_by' is a feature name.
318
+ point_size: Size of the points.
319
+ plot_width: Width of the output plot in inches.
320
+ plot_height: Height of the output plot in inches.
321
+ """
322
+ # Input validation
323
+ if not spe_path.is_file():
324
+ raise FileNotFoundError(f"Input file not found: {spe_path}")
325
+ if not reduction_type:
326
+ raise ValueError("A reduction_type must be provided.")
327
+
328
+ # Prepare arguments for the R function
329
+ json_args = {
330
+ "type": reduction_type,
331
+ "x_axis": x_axis,
332
+ "y_axis": y_axis,
333
+ "color_by": color_by,
334
+ "shape_by": shape_by,
335
+ "assay": assay,
336
+ "point_size": point_size,
337
+ }
338
+ json_args = {k: v for k, v in json_args.items() if v is not None}
339
+
340
+ output_path = Path(output_filename)
341
+
342
+ return _run_ggspavis(
343
+ function_name="plotDimRed",
344
+ spe_path=spe_path,
345
+ output_path=output_path,
346
+ plot_width=plot_width,
347
+ plot_height=plot_height,
348
+ json_args=json_args,
349
+ )
350
+
351
+
352
+ @mcp.tool()
353
+ def ggspavis_plot_label(
354
+ spe_path: Path,
355
+ output_filename: str,
356
+ label: str,
357
+ platform: Literal["Visium", "ST", "VisiumHD", "mIF", "seqFISH", "MERFISH", "Xenium"] = "Visium",
358
+ palette: Optional[str] = None,
359
+ point_size: float = 1.5,
360
+ plot_width: float = 7.0,
361
+ plot_height: float = 7.0,
362
+ ):
363
+ """
364
+ Visualizes spatial clusters or other categorical labels using ggspavis::plotLabel.
365
+
366
+ Args:
367
+ spe_path: Path to the input SpatialExperiment object (.rds file).
368
+ output_filename: Name of the output plot file (e.g., 'clusters.png').
369
+ label: Column name in colData containing the labels to plot.
370
+ platform: The spatial transcriptomics platform.
371
+ palette: Optional color palette for labels. If None, ggplot2 default is used.
372
+ point_size: Size of the points.
373
+ plot_width: Width of the output plot in inches.
374
+ plot_height: Height of the output plot in inches.
375
+ """
376
+ # Input validation
377
+ if not spe_path.is_file():
378
+ raise FileNotFoundError(f"Input file not found: {spe_path}")
379
+ if not label:
380
+ raise ValueError("A label column name must be provided.")
381
+
382
+ # Prepare arguments for the R function
383
+ json_args = {
384
+ "label": label,
385
+ "platform": platform,
386
+ "palette": palette,
387
+ "point_size": point_size,
388
+ }
389
+ json_args = {k: v for k, v in json_args.items() if v is not None}
390
+
391
+ output_path = Path(output_filename)
392
+
393
+ return _run_ggspavis(
394
+ function_name="plotLabel",
395
+ spe_path=spe_path,
396
+ output_path=output_path,
397
+ plot_width=plot_width,
398
+ plot_height=plot_height,
399
+ json_args=json_args,
400
+ )
401
+
402
+ if __name__ == "__main__":
403
+ mcp.run(transport="stdio")
Biomni/mcp_generated/mcp_bioconductor-ggspavis/app/bioconductor-ggspavis_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-ggspavis/app/bioconductor-ggspavis_server.py')
11
+ LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name)
12
+ SERVER_NAME = 'biosci_bioconductor_ggspavis'
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-ggspavis/app/requirements.txt ADDED
@@ -0,0 +1 @@
 
 
1
+
Biomni/mcp_generated/mcp_bioconductor-ggspavis/docker-compose.yml ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ version: '3.8'
2
+
3
+ services:
4
+ mcp-bioconductor-ggspavis:
5
+ build: .
6
+ image: mcp-bioconductor-ggspavis:latest
7
+ container_name: mcp-bioconductor-ggspavis
8
+ ports:
9
+ - "8000:8000"
10
+ environment:
11
+ - MCP_SERVER_NAME=bioconductor-ggspavis
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-ggspavis/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-ggspavis
9
+ - python=3.10
10
+
Biomni/mcp_generated/mcp_bioconductor-ggspavis/requirements.txt ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ fastmcp
2
+ mcp
Biomni/mcp_generated/mcp_bioconductor-mastr/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-mastr via conda (e.g., from bioconda)
14
+ RUN conda install -c bioconda bioconductor-mastr -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-mastr_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-mastr_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-mastr_server.py"]
40
+
Biomni/mcp_generated/mcp_bioconductor-mastr/app/bioconductor-mastr_server.py ADDED
@@ -0,0 +1,208 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import subprocess
2
+ import tempfile
3
+ from pathlib import Path
4
+ from typing import Optional, List, Union
5
+
6
+ from mcp.server.fastmcp import FastMCP
7
+
8
+ SERVER_NAME = 'local_bioconductor_mastr'
9
+ mcp = FastMCP(SERVER_NAME)
10
+
11
+ @mcp.tool()
12
+ def mastr_align(
13
+ fasta_input: str,
14
+ output_alignment: str,
15
+ iterations: int = 100,
16
+ temperature: float = 1.0,
17
+ cooling_rate: float = 0.9,
18
+ gap_open: float = -10.0,
19
+ gap_extend: float = -1.0,
20
+ use_structure: bool = True,
21
+ r_path: str = "Rscript"
22
+ ):
23
+ """
24
+ Perform Multiple Alignment of Structural RNAs (MASTR) using the Bioconductor mastr package.
25
+
26
+ Args:
27
+ fasta_input: Path to the input FASTA file containing RNA sequences.
28
+ output_alignment: Path where the resulting alignment (FASTA format) will be saved.
29
+ iterations: Number of iterations for the simulated annealing (default: 100).
30
+ temperature: Initial temperature for simulated annealing (default: 1.0).
31
+ cooling_rate: Cooling rate for simulated annealing (default: 0.9).
32
+ gap_open: Penalty for opening a gap (default: -10.0).
33
+ gap_extend: Penalty for extending a gap (default: -1.0).
34
+ use_structure: Whether to incorporate secondary structure prediction (default: True).
35
+ r_path: Path to the Rscript executable (default: 'Rscript').
36
+ """
37
+ # Input validation
38
+ input_path = Path(fasta_input)
39
+ if not input_path.exists():
40
+ return {"error": f"Input file not found: {fasta_input}"}
41
+
42
+ output_path = Path(output_alignment)
43
+ output_path.parent.mkdir(parents=True, exist_ok=True)
44
+
45
+ if iterations <= 0:
46
+ return {"error": "Iterations must be a positive integer."}
47
+
48
+ if not (0 < cooling_rate < 1):
49
+ return {"error": "Cooling rate must be between 0 and 1."}
50
+
51
+ # Create an R script to execute the mastr package
52
+ # Note: mastr is a Bioconductor package, so we use Rscript to interface with it.
53
+ r_script_content = f"""
54
+ library(mastr)
55
+ library(Biostrings)
56
+
57
+ # Load sequences
58
+ seqs <- readRNAStringSet("{input_path.absolute()}")
59
+
60
+ # Define parameters
61
+ params <- list(
62
+ iterations = {iterations},
63
+ temp = {temperature},
64
+ cooling = {cooling_rate},
65
+ gapOpen = {gap_open},
66
+ gapExtend = {gap_extend},
67
+ useStructure = {"TRUE" if use_structure else "FALSE"}
68
+ )
69
+
70
+ # Run MASTR alignment
71
+ # Note: mastr() is the primary function in the Bioconductor package
72
+ alignment_result <- mastr(seqs,
73
+ iterations = params$iterations,
74
+ temp = params$temp,
75
+ cooling = params$cooling,
76
+ gapOpen = params$gapOpen,
77
+ gapExtend = params$gapExtend)
78
+
79
+ # Write output
80
+ writeXStringSet(alignment_result, "{output_path.absolute()}")
81
+ """
82
+
83
+ with tempfile.NamedTemporaryFile(mode='w', suffix='.R', delete=False) as tmp:
84
+ tmp.write(r_script_content)
85
+ tmp_path = tmp.name
86
+
87
+ try:
88
+ # Execute the R script
89
+ process = subprocess.run(
90
+ [r_path, tmp_path],
91
+ capture_output=True,
92
+ text=True,
93
+ check=True
94
+ )
95
+
96
+ return {
97
+ "command_executed": f"{r_path} {tmp_path}",
98
+ "stdout": process.stdout,
99
+ "stderr": process.stderr,
100
+ "output_files": [str(output_path.absolute())],
101
+ "status": "Success"
102
+ }
103
+
104
+ except subprocess.CalledProcessError as e:
105
+ return {
106
+ "command_executed": f"{r_path} {tmp_path}",
107
+ "stdout": e.stdout,
108
+ "stderr": e.stderr,
109
+ "error": "R execution failed. Ensure 'mastr' and 'Biostrings' packages are installed in R.",
110
+ "status": "Error"
111
+ }
112
+ finally:
113
+ # Cleanup temporary R script
114
+ if Path(tmp_path).exists():
115
+ Path(tmp_path).unlink()
116
+
117
+ @mcp.tool()
118
+ def mastr_score_alignment(
119
+ alignment_file: str,
120
+ r_path: str = "Rscript"
121
+ ):
122
+ """
123
+ Calculate the structural conservation score for an existing RNA alignment using mastr metrics.
124
+
125
+ Args:
126
+ alignment_file: Path to the aligned FASTA/Stockholm file.
127
+ r_path: Path to the Rscript executable.
128
+ """
129
+ input_path = Path(alignment_file)
130
+ if not input_path.exists():
131
+ return {"error": f"Alignment file not found: {alignment_file}"}
132
+
133
+ r_script_content = f"""
134
+ library(mastr)
135
+ library(Biostrings)
136
+
137
+ aln <- readRNAStringSet("{input_path.absolute()}")
138
+ # Calculate conservation score based on mastr's internal scoring
139
+ # This typically involves base-pairing probabilities and covariance
140
+ score <- mastr:::calculateScore(aln) # Accessing internal scoring if exported or via wrapper
141
+ cat("Alignment_Score:", score, "\\n")
142
+ """
143
+
144
+ with tempfile.NamedTemporaryFile(mode='w', suffix='.R', delete=False) as tmp:
145
+ tmp.write(r_script_content)
146
+ tmp_path = tmp.name
147
+
148
+ try:
149
+ process = subprocess.run(
150
+ [r_path, tmp_path],
151
+ capture_output=True,
152
+ text=True,
153
+ check=True
154
+ )
155
+
156
+ return {
157
+ "command_executed": f"{r_path} {tmp_path}",
158
+ "stdout": process.stdout,
159
+ "stderr": process.stderr,
160
+ "status": "Success"
161
+ }
162
+ except subprocess.CalledProcessError as e:
163
+ return {
164
+ "error": str(e),
165
+ "stderr": e.stderr,
166
+ "status": "Error"
167
+ }
168
+ finally:
169
+ if Path(tmp_path).exists():
170
+ Path(tmp_path).unlink()
171
+
172
+ @mcp.tool()
173
+ def mastr_check_dependencies(r_path: str = "Rscript"):
174
+ """
175
+ Verify if R and the required Bioconductor mastr package are installed and accessible.
176
+
177
+ Args:
178
+ r_path: Path to the Rscript executable.
179
+ """
180
+ r_script_content = """
181
+ if (!requireNamespace("mastr", quietly = TRUE)) {
182
+ stop("Package 'mastr' not found. Please install it via BiocManager::install('mastr')")
183
+ }
184
+ if (!requireNamespace("Biostrings", quietly = TRUE)) {
185
+ stop("Package 'Biostrings' not found.")
186
+ }
187
+ cat("All dependencies are satisfied.\\n")
188
+ """
189
+
190
+ try:
191
+ process = subprocess.run(
192
+ [r_path, "-e", r_script_content],
193
+ capture_output=True,
194
+ text=True,
195
+ check=True
196
+ )
197
+ return {
198
+ "status": "Ready",
199
+ "stdout": process.stdout
200
+ }
201
+ except Exception as e:
202
+ return {
203
+ "status": "Missing Dependencies",
204
+ "error": str(e)
205
+ }
206
+
207
+ if __name__ == "__main__":
208
+ mcp.run(transport="stdio")
Biomni/mcp_generated/mcp_bioconductor-mastr/app/bioconductor-mastr_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-mastr/app/bioconductor-mastr_server.py')
11
+ LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name)
12
+ SERVER_NAME = 'biosci_bioconductor_mastr'
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-mastr/app/requirements.txt ADDED
@@ -0,0 +1 @@
 
 
1
+
Biomni/mcp_generated/mcp_bioconductor-mastr/requirements.txt ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ fastmcp
2
+ mcp
Biomni/mcp_generated/mcp_bioconductor-rhdf5filters/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-rhdf5filters via conda (e.g., from bioconda)
14
+ RUN conda install -c bioconda bioconductor-rhdf5filters -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-rhdf5filters_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-rhdf5filters_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-rhdf5filters_server.py"]
40
+
Biomni/mcp_generated/mcp_bioconductor-rhdf5filters/app/bioconductor-rhdf5filters_server.py ADDED
@@ -0,0 +1,78 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import subprocess
2
+ import tempfile
3
+ import shutil
4
+ from pathlib import Path
5
+ from typing import Dict, Any, List
6
+
7
+ # MCP decorator is commented out as per instructions
8
+ from mcp.server.fastmcp import FastMCP
9
+
10
+ SERVER_NAME = 'local_bioconductor_rhdf5filters'
11
+ mcp = FastMCP(SERVER_NAME)
12
+
13
+ @mcp.tool()
14
+ def check_installation() -> Dict[str, Any]:
15
+ """
16
+ Verifies the installation of the bioconductor-rhdf5filters R library.
17
+
18
+ bioconductor-rhdf5filters is an R library that provides HDF5 compression
19
+ filters and does not have a direct command-line interface. This function
20
+ executes a simple R script to confirm that the library is installed and
21
+ can be loaded in the R environment, and it reports the installed version.
22
+ """
23
+ # Pre-flight check for Rscript executable
24
+ if not shutil.which("Rscript"):
25
+ raise FileNotFoundError("Rscript executable not found in PATH. R is required to run this tool.")
26
+
27
+ r_script_content = """
28
+ if (!requireNamespace("rhdf5filters", quietly = TRUE)) {
29
+ cat("Error: The 'rhdf5filters' package is not installed.\\n", file = stderr())
30
+ quit(status = 1)
31
+ }
32
+ ver <- as.character(packageVersion("rhdf5filters"))
33
+ cat(paste0("Successfully loaded 'rhdf5filters' version: ", ver, "\\n"))
34
+ quit(status = 0)
35
+ """
36
+
37
+ command_executed = ""
38
+ script_path = ""
39
+ try:
40
+ with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix=".R", encoding='utf-8') as tmp_r_script:
41
+ script_path = tmp_r_script.name
42
+ tmp_r_script.write(r_script_content)
43
+
44
+ cmd: List[str] = ["Rscript", script_path]
45
+ command_executed = " ".join(cmd)
46
+
47
+ process = subprocess.run(
48
+ cmd,
49
+ capture_output=True,
50
+ text=True,
51
+ check=True,
52
+ encoding='utf-8'
53
+ )
54
+
55
+ return {
56
+ "command_executed": command_executed,
57
+ "stdout": process.stdout,
58
+ "stderr": process.stderr,
59
+ "output_files": []
60
+ }
61
+
62
+ except subprocess.CalledProcessError as e:
63
+ # This will catch the case where the R script exits with a non-zero status
64
+ # (e.g., package not installed) and return a structured error.
65
+ return {
66
+ "command_executed": command_executed,
67
+ "stdout": e.stdout,
68
+ "stderr": e.stderr,
69
+ "return_code": e.returncode,
70
+ "error": "R script execution failed. The 'rhdf5filters' package may not be installed or accessible."
71
+ }
72
+ finally:
73
+ # Ensure the temporary file is cleaned up
74
+ if script_path and Path(script_path).exists():
75
+ Path(script_path).unlink()
76
+
77
+ if __name__ == "__main__":
78
+ mcp.run(transport="stdio")
Biomni/mcp_generated/mcp_bioconductor-rhdf5filters/app/requirements.txt ADDED
@@ -0,0 +1 @@
 
 
1
+
Biomni/mcp_generated/mcp_bioconductor-rhdf5filters/docker-compose.yml ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ version: '3.8'
2
+
3
+ services:
4
+ mcp-bioconductor-rhdf5filters:
5
+ build: .
6
+ image: mcp-bioconductor-rhdf5filters:latest
7
+ container_name: mcp-bioconductor-rhdf5filters
8
+ ports:
9
+ - "8000:8000"
10
+ environment:
11
+ - MCP_SERVER_NAME=bioconductor-rhdf5filters
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-rhdf5filters/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-rhdf5filters
9
+ - python=3.10
10
+
Biomni/mcp_generated/mcp_bioconductor-rsubread/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-rsubread via conda (e.g., from bioconda)
14
+ RUN conda install -c bioconda bioconductor-rsubread -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-rsubread_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-rsubread_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-rsubread_server.py"]
40
+
Biomni/mcp_generated/mcp_bioconductor-rsubread/app/bioconductor-rsubread_server.py ADDED
@@ -0,0 +1,322 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import subprocess
2
+ import os
3
+ from pathlib import Path
4
+ from typing import Optional, List, Union
5
+ import tempfile
6
+
7
+ def run_r_command(command: str):
8
+ """Helper to execute R commands via Rscript"""
9
+ try:
10
+ # We use -e to execute the expression directly
11
+ full_cmd = ["Rscript", "-e", f"library(Rsubread); {command}"]
12
+ result = subprocess.run(full_cmd, capture_output=True, text=True, check=True)
13
+ return result.stdout, result.stderr
14
+ except subprocess.CalledProcessError as e:
15
+ raise RuntimeError(f"Rsubread execution failed: {e.stderr}")
16
+
17
+ from mcp.server.fastmcp import FastMCP
18
+
19
+ SERVER_NAME = 'local_bioconductor_rsubread'
20
+ mcp = FastMCP(SERVER_NAME)
21
+
22
+ @mcp.tool()
23
+ def rsubread_buildindex(
24
+ basename: str,
25
+ reference: str,
26
+ gapped_index: bool = False,
27
+ index_memory: int = 8000,
28
+ colorspace: bool = False,
29
+ split_index: bool = False
30
+ ):
31
+ """
32
+ Builds an index for a reference genome.
33
+
34
+ Args:
35
+ basename: Base name of the index files to be generated.
36
+ reference: Path to the reference genome file (FASTA format).
37
+ gapped_index: If True, a gapped index will be built (saves memory).
38
+ index_memory: Memory usage in MB.
39
+ colorspace: If True, index is built for color-space reads (SOLiD).
40
+ split_index: If True, the index will be split into multiple blocks.
41
+ """
42
+ ref_path = Path(reference)
43
+ if not ref_path.exists():
44
+ return {"error": f"Reference file {reference} not found."}
45
+
46
+ # Construct R command
47
+ r_cmd = (
48
+ f"buildindex(basename='{basename}', reference='{reference}', "
49
+ f"gappedIndex={'TRUE' if gapped_index else 'FALSE'}, "
50
+ f"indexMemory={index_memory}, colorspace={'TRUE' if colorspace else 'FALSE'}, "
51
+ f"splitIndex={'TRUE' if split_index else 'FALSE'})"
52
+ )
53
+
54
+ try:
55
+ stdout, stderr = run_r_command(r_cmd)
56
+ return {
57
+ "command_executed": f"Rsubread::buildindex({basename})",
58
+ "stdout": stdout,
59
+ "stderr": stderr,
60
+ "output_files": [str(Path(f"{basename}.{ext}")) for ext in ["files"]] # Rsubread creates multiple files
61
+ }
62
+ except Exception as e:
63
+ return {"error": str(e)}
64
+
65
+ @mcp.tool()
66
+ def rsubread_align(
67
+ index: str,
68
+ readfile1: str,
69
+ readfile2: Optional[str] = None,
70
+ output_file: str = "output.bam",
71
+ input_format: str = "gzFASTQ",
72
+ output_format: str = "BAM",
73
+ nthreads: int = 1,
74
+ type: str = "dna",
75
+ phred_offset: int = 33,
76
+ unique: bool = False,
77
+ n_best_locations: int = 1,
78
+ max_mismatches: int = 3
79
+ ):
80
+ """
81
+ Aligns reads to a reference genome using the Subread aligner.
82
+
83
+ Args:
84
+ index: Base name of the index.
85
+ readfile1: Path to the first read file.
86
+ readfile2: Path to the second read file (for paired-end).
87
+ output_file: Name of the output file.
88
+ input_format: Format of input reads ('FASTQ', 'gzFASTQ', 'SAM', 'BAM').
89
+ output_format: Format of output ('BAM', 'SAM').
90
+ nthreads: Number of threads to use.
91
+ type: Type of sequencing ('dna' or 'rna').
92
+ phred_offset: Phred score offset (33 or 64).
93
+ unique: If True, only uniquely mapped reads are reported.
94
+ n_best_locations: Number of best mapping locations to report.
95
+ max_mismatches: Maximum number of mismatches allowed.
96
+ """
97
+ if not Path(readfile1).exists():
98
+ return {"error": f"Read file {readfile1} not found."}
99
+
100
+ read2_param = f"'{readfile2}'" if readfile2 else "NULL"
101
+
102
+ r_cmd = (
103
+ f"align(index='{index}', readfile1='{readfile1}', readfile2={read2_param}, "
104
+ f"output_file='{output_file}', input_format='{input_format}', output_format='{output_format}', "
105
+ f"nthreads={nthreads}, type='{type}', phredOffset={phred_offset}, "
106
+ f"unique={'TRUE' if unique else 'FALSE'}, nBestLocations={n_best_locations}, "
107
+ f"maxMismatches={max_mismatches})"
108
+ )
109
+
110
+ try:
111
+ stdout, stderr = run_r_command(r_cmd)
112
+ return {
113
+ "command_executed": "Rsubread::align",
114
+ "stdout": stdout,
115
+ "stderr": stderr,
116
+ "output_files": [output_file]
117
+ }
118
+ except Exception as e:
119
+ return {"error": str(e)}
120
+
121
+ @mcp.tool()
122
+ def rsubread_subjunc(
123
+ index: str,
124
+ readfile1: str,
125
+ readfile2: Optional[str] = None,
126
+ output_file: str = "output_subjunc.bam",
127
+ input_format: str = "gzFASTQ",
128
+ output_format: str = "BAM",
129
+ nthreads: int = 1,
130
+ phred_offset: int = 33,
131
+ unique: bool = False
132
+ ):
133
+ """
134
+ Aligns RNA-seq reads and detects exon-exon junctions using the Subjunc aligner.
135
+
136
+ Args:
137
+ index: Base name of the index.
138
+ readfile1: Path to the first read file.
139
+ readfile2: Path to the second read file (for paired-end).
140
+ output_file: Name of the output file.
141
+ input_format: Format of input reads.
142
+ output_format: Format of output.
143
+ nthreads: Number of threads.
144
+ phred_offset: Phred score offset.
145
+ unique: If True, only uniquely mapped reads are reported.
146
+ """
147
+ read2_param = f"'{readfile2}'" if readfile2 else "NULL"
148
+
149
+ r_cmd = (
150
+ f"subjunc(index='{index}', readfile1='{readfile1}', readfile2={read2_param}, "
151
+ f"output_file='{output_file}', input_format='{input_format}', output_format='{output_format}', "
152
+ f"nthreads={nthreads}, phredOffset={phred_offset}, unique={'TRUE' if unique else 'FALSE'})"
153
+ )
154
+
155
+ try:
156
+ stdout, stderr = run_r_command(r_cmd)
157
+ return {
158
+ "command_executed": "Rsubread::subjunc",
159
+ "stdout": stdout,
160
+ "stderr": stderr,
161
+ "output_files": [output_file]
162
+ }
163
+ except Exception as e:
164
+ return {"error": str(e)}
165
+
166
+ @mcp.tool()
167
+ def rsubread_featurecounts(
168
+ files: List[str],
169
+ annot_ext: str,
170
+ is_gtf_annotation_file: bool = True,
171
+ gtf_feature_type: str = "exon",
172
+ gtf_attr_type: str = "gene_id",
173
+ use_meta_features: bool = True,
174
+ allow_multi_overlap: bool = False,
175
+ is_paired_end: bool = False,
176
+ nthreads: int = 1,
177
+ strand_specific: int = 0,
178
+ count_multi_mapping_reads: bool = False
179
+ ):
180
+ """
181
+ Assigns mapped reads to genomic features (e.g. genes, exons).
182
+
183
+ Args:
184
+ files: List of paths to alignment files (BAM/SAM).
185
+ annot_ext: Path to the annotation file (GTF/GFF/SAF).
186
+ is_gtf_annotation_file: Whether the annotation file is in GTF format.
187
+ gtf_feature_type: Feature type in GTF to be used (e.g. 'exon').
188
+ gtf_attr_type: Attribute type in GTF to be used (e.g. 'gene_id').
189
+ use_meta_features: If True, reads are counted for meta-features (e.g. genes).
190
+ allow_multi_overlap: If True, a read is allowed to be assigned to multiple features.
191
+ is_paired_end: If True, reads are treated as paired-end.
192
+ nthreads: Number of threads.
193
+ strand_specific: Strand specificity (0: unstranded, 1: stranded, 2: reversely stranded).
194
+ count_multi_mapping_reads: If True, multi-mapping reads will be counted.
195
+ """
196
+ # Convert Python list to R vector
197
+ files_r = "c(" + ",".join([f"'{f}'" for f in files]) + ")"
198
+
199
+ r_cmd = (
200
+ f"fc <- featureCounts(files={files_r}, annot.ext='{annot_ext}', "
201
+ f"isGTFAnnotationFile={'TRUE' if is_gtf_annotation_file else 'FALSE'}, "
202
+ f"GTF.featureType='{gtf_feature_type}', GTF.attrType='{gtf_attr_type}', "
203
+ f"useMetaFeatures={'TRUE' if use_meta_features else 'FALSE'}, "
204
+ f"allowMultiOverlap={'TRUE' if allow_multi_overlap else 'FALSE'}, "
205
+ f"isPairedEnd={'TRUE' if is_paired_end else 'FALSE'}, "
206
+ f"nthreads={nthreads}, strandSpecific={strand_specific}, "
207
+ f"countMultiMappingReads={'TRUE' if count_multi_mapping_reads else 'FALSE'}); "
208
+ f"write.table(fc$counts, file='counts.txt', sep='\\t', quote=FALSE); "
209
+ f"write.table(fc$stat, file='stats.txt', sep='\\t', quote=FALSE)"
210
+ )
211
+
212
+ try:
213
+ stdout, stderr = run_r_command(r_cmd)
214
+ return {
215
+ "command_executed": "Rsubread::featureCounts",
216
+ "stdout": stdout,
217
+ "stderr": stderr,
218
+ "output_files": ["counts.txt", "stats.txt"]
219
+ }
220
+ except Exception as e:
221
+ return {"error": str(e)}
222
+
223
+ @mcp.tool()
224
+ def rsubread_qualityscores(
225
+ filename: str,
226
+ input_format: str = "gzFASTQ",
227
+ offset: int = 33,
228
+ nreads: int = 10000
229
+ ):
230
+ """
231
+ Extracts quality scores from a read file.
232
+
233
+ Args:
234
+ filename: Path to the read file.
235
+ input_format: Format of the input file.
236
+ offset: Phred score offset.
237
+ nreads: Number of reads to extract quality scores from.
238
+ """
239
+ if not Path(filename).exists():
240
+ return {"error": f"File {filename} not found."}
241
+
242
+ r_cmd = (
243
+ f"qs <- qualityScores(filename='{filename}', input_format='{input_format}', "
244
+ f"offset={offset}, nreads={nreads}); "
245
+ f"write.table(qs, file='quality_scores.txt', sep='\\t', quote=FALSE)"
246
+ )
247
+
248
+ try:
249
+ stdout, stderr = run_r_command(r_cmd)
250
+ return {
251
+ "command_executed": "Rsubread::qualityScores",
252
+ "stdout": stdout,
253
+ "stderr": stderr,
254
+ "output_files": ["quality_scores.txt"]
255
+ }
256
+ except Exception as e:
257
+ return {"error": str(e)}
258
+
259
+ @mcp.tool()
260
+ def rsubread_atgccontent(
261
+ filename: str,
262
+ input_format: str = "gzFASTQ",
263
+ basewise: bool = True
264
+ ):
265
+ """
266
+ Calculates ATGC content of reads.
267
+
268
+ Args:
269
+ filename: Path to the read file.
270
+ input_format: Format of the input file.
271
+ basewise: If True, calculate content for each base position.
272
+ """
273
+ r_cmd = (
274
+ f"atgc <- atgcContent(filename='{filename}', input_format='{input_format}', "
275
+ f"basewise={'TRUE' if basewise else 'FALSE'}); "
276
+ f"write.table(atgc, file='atgc_content.txt', sep='\\t', quote=FALSE)"
277
+ )
278
+
279
+ try:
280
+ stdout, stderr = run_r_command(r_cmd)
281
+ return {
282
+ "command_executed": "Rsubread::atgcContent",
283
+ "stdout": stdout,
284
+ "stderr": stderr,
285
+ "output_files": ["atgc_content.txt"]
286
+ }
287
+ except Exception as e:
288
+ return {"error": str(e)}
289
+
290
+ @mcp.tool()
291
+ def rsubread_propmapped(
292
+ files: List[str],
293
+ count_multi_mapping_reads: bool = False
294
+ ):
295
+ """
296
+ Calculates the proportion of mapped reads in BAM/SAM files.
297
+
298
+ Args:
299
+ files: List of paths to alignment files.
300
+ count_multi_mapping_reads: Whether to include multi-mapping reads.
301
+ """
302
+ files_r = "c(" + ",".join([f"'{f}'" for f in files]) + ")"
303
+
304
+ r_cmd = (
305
+ f"pm <- propMapped(files={files_r}, "
306
+ f"countMultiMappingReads={'TRUE' if count_multi_mapping_reads else 'FALSE'}); "
307
+ f"write.table(pm, file='prop_mapped.txt', sep='\\t', quote=FALSE, row.names=FALSE)"
308
+ )
309
+
310
+ try:
311
+ stdout, stderr = run_r_command(r_cmd)
312
+ return {
313
+ "command_executed": "Rsubread::propMapped",
314
+ "stdout": stdout,
315
+ "stderr": stderr,
316
+ "output_files": ["prop_mapped.txt"]
317
+ }
318
+ except Exception as e:
319
+ return {"error": str(e)}
320
+
321
+ if __name__ == "__main__":
322
+ mcp.run(transport="stdio")
Biomni/mcp_generated/mcp_bioconductor-rsubread/app/bioconductor-rsubread_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-rsubread/app/bioconductor-rsubread_server.py')
11
+ LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name)
12
+ SERVER_NAME = 'biosci_bioconductor_rsubread'
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-rsubread/app/requirements.txt ADDED
@@ -0,0 +1 @@
 
 
1
+
Biomni/mcp_generated/mcp_bioconductor-rtracklayer/app/bioconductor-rtracklayer_server.py ADDED
@@ -0,0 +1,340 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import subprocess
2
+ import tempfile
3
+ import re
4
+ from pathlib import Path
5
+ from typing import Optional, List
6
+
7
+ # @mcp.tool() is a placeholder for the actual decorator.
8
+ # The code is written to be compatible with the MCP framework.
9
+
10
+ def mcp_tool_placeholder():
11
+ def decorator(func):
12
+ return func
13
+ return decorator
14
+
15
+ @mcp_tool_placeholder()
16
+ def rtracklayer_convert(
17
+ input_file: Path,
18
+ output_file: Path,
19
+ input_format: Optional[str] = None,
20
+ output_format: Optional[str] = None,
21
+ which: Optional[str] = None,
22
+ genome: Optional[str] = None,
23
+ gff_feature_type_filter: Optional[str] = None,
24
+ gff_version: Optional[str] = None,
25
+ export_source: Optional[str] = None,
26
+ index_output: bool = False,
27
+ ):
28
+ """
29
+ Converts genomic data files from one format to another using rtracklayer.
30
+
31
+ This tool wraps the core import() and export() functionality of the rtracklayer
32
+ R package, allowing for conversion between formats like BED, GFF, GTF, WIG,
33
+ and BigWig. It also supports filtering by genomic region.
34
+
35
+ Args:
36
+ input_file: Path to the input genomic data file.
37
+ output_file: Path to write the converted output file.
38
+ input_format: The format of the input file (e.g., 'bed', 'gff', 'bigWig').
39
+ If not provided, rtracklayer will guess from the file extension.
40
+ output_format: The format for the output file. If not provided, rtracklayer
41
+ will guess from the file extension.
42
+ which: A specific genomic region to process, in the format 'chr:start-end'
43
+ (e.g., 'chr1:1000-2000'). This is highly effective for indexed files.
44
+ genome: The genome assembly name (e.g., 'hg19', 'mm10').
45
+ gff_feature_type_filter: For GFF/GTF import, a comma-separated list of
46
+ feature types to keep (e.g., 'exon,gene').
47
+ gff_version: Specify the GFF version ('1', '2', or '3') for import/export.
48
+ export_source: The 'source' field to use for GFF/GTF export.
49
+ index_output: If True, create a tabix index (.tbi) for the output file.
50
+ Applicable to formats like BED and GFF.
51
+ """
52
+ # 1. Input Validation
53
+ if not input_file.exists():
54
+ raise FileNotFoundError(f"Input file not found: {input_file}")
55
+
56
+ ALLOWED_FORMATS = ["bed", "gff", "gff1", "gff2", "gff3", "gtf", "wig", "bigWig", "bedGraph", "2bit", "fasta"]
57
+ if input_format and input_format not in ALLOWED_FORMATS:
58
+ raise ValueError(f"Invalid input_format '{input_format}'. Allowed formats: {ALLOWED_FORMATS}")
59
+ if output_format and output_format not in ALLOWED_FORMATS:
60
+ raise ValueError(f"Invalid output_format '{output_format}'. Allowed formats: {ALLOWED_FORMATS}")
61
+
62
+ if gff_version and gff_version not in ["1", "2", "3"]:
63
+ raise ValueError("gff_version must be one of '1', '2', or '3'.")
64
+
65
+ if which and not re.match(r"^\w+:\d+-\d+$", which):
66
+ raise ValueError("Invalid 'which' format. Expected 'chr:start-end' (e.g., 'chr1:1000-5000').")
67
+
68
+ # 2. Build R Script
69
+ r_script_lines = [
70
+ "suppressPackageStartupMessages(library(rtracklayer))",
71
+ "suppressPackageStartupMessages(library(GenomicRanges))",
72
+ "",
73
+ "# --- Import Arguments ---",
74
+ f"import_args <- list(con = '{input_file.resolve()}')",
75
+ ]
76
+ if input_format:
77
+ r_script_lines.append(f"import_args$format <- '{input_format}'")
78
+ if which:
79
+ r_script_lines.append(f"import_args$which <- GRanges('{which}')")
80
+ if genome:
81
+ r_script_lines.append(f"import_args$genome <- '{genome}'")
82
+ if gff_feature_type_filter:
83
+ r_vector = 'c(' + ', '.join([f'"{f.strip()}"' for f in gff_feature_type_filter.split(',')]) + ')'
84
+ r_script_lines.append(f"import_args$feature.type <- {r_vector}")
85
+ if gff_version:
86
+ r_script_lines.append(f"import_args$version <- '{gff_version}'")
87
+
88
+ r_script_lines.extend([
89
+ "",
90
+ "# --- Import Data ---",
91
+ "gr_object <- do.call(rtracklayer::import, import_args)",
92
+ "",
93
+ "# --- Export Arguments ---",
94
+ f"export_args <- list(object = gr_object, con = '{output_file.resolve()}')",
95
+ ])
96
+
97
+ if output_format:
98
+ r_script_lines.append(f"export_args$format <- '{output_format}'")
99
+ if export_source:
100
+ r_script_lines.append(f"export_args$source <- '{export_source}'")
101
+ if gff_version:
102
+ r_script_lines.append(f"export_args$version <- '{gff_version}'")
103
+ if index_output:
104
+ r_script_lines.append("export_args$index <- TRUE")
105
+
106
+ r_script_lines.extend([
107
+ "",
108
+ "# --- Export Data ---",
109
+ "do.call(rtracklayer::export, export_args)",
110
+ "cat('Conversion successful.\\n')"
111
+ ])
112
+
113
+ r_script = "\n".join(r_script_lines)
114
+
115
+ # 3. Execute R Script
116
+ with tempfile.NamedTemporaryFile(mode='w', suffix=".R", delete=False, dir=".") as tmp_script:
117
+ script_path = Path(tmp_script.name)
118
+ tmp_script.write(r_script)
119
+
120
+ command = ["Rscript", str(script_path.name)]
121
+ command_str = " ".join(command)
122
+
123
+ try:
124
+ result = subprocess.run(
125
+ command,
126
+ capture_output=True,
127
+ text=True,
128
+ check=True,
129
+ cwd=Path.cwd()
130
+ )
131
+
132
+ output_files = [str(output_file)]
133
+ index_file = Path(f"{output_file}.tbi")
134
+ if index_output and index_file.exists():
135
+ output_files.append(str(index_file))
136
+
137
+ return {
138
+ "command_executed": command_str,
139
+ "stdout": result.stdout,
140
+ "stderr": result.stderr,
141
+ "output_files": output_files
142
+ }
143
+ except subprocess.CalledProcessError as e:
144
+ raise RuntimeError(
145
+ f"Rscript execution failed with return code {e.returncode}.\n"
146
+ f"Command: {command_str}\n"
147
+ f"Stderr: {e.stderr}\n"
148
+ f"Stdout: {e.stdout}"
149
+ ) from e
150
+ finally:
151
+ script_path.unlink()
152
+
153
+
154
+ @mcp_tool_placeholder()
155
+ def rtracklayer_liftover(
156
+ input_file: Path,
157
+ output_file: Path,
158
+ chain_file: Path,
159
+ input_format: Optional[str] = None,
160
+ unmapped_output_file: Optional[Path] = None,
161
+ ):
162
+ """
163
+ Lifts over genomic coordinates from one assembly to another using a chain file.
164
+
165
+ This tool wraps the liftOver() function from rtracklayer, which is essential
166
+ for translating genomic features between different reference genome versions
167
+ (e.g., hg18 to hg19).
168
+
169
+ Args:
170
+ input_file: Path to the input genomic data file (e.g., BED, GFF) with
171
+ coordinates to be lifted over.
172
+ output_file: Path to write the output file with lifted coordinates.
173
+ chain_file: Path to the liftOver chain file (.chain or .chain.gz).
174
+ input_format: The format of the input file (e.g., 'bed', 'gff'). If not
175
+ provided, rtracklayer will guess from the file extension.
176
+ unmapped_output_file: Optional path to write features that could not be
177
+ mapped to the new assembly.
178
+ """
179
+ # 1. Input Validation
180
+ if not input_file.exists():
181
+ raise FileNotFoundError(f"Input file not found: {input_file}")
182
+ if not chain_file.exists():
183
+ raise FileNotFoundError(f"Chain file not found: {chain_file}")
184
+
185
+ # 2. Build R Script
186
+ r_script_lines = [
187
+ "suppressPackageStartupMessages(library(rtracklayer))",
188
+ "",
189
+ "# --- Import Data ---",
190
+ f"ch <- rtracklayer::import.chain('{chain_file.resolve()}')",
191
+ f"gr <- rtracklayer::import('{input_file.resolve()}'" + (f", format = '{input_format}'" if input_format else "") + ")",
192
+ "",
193
+ "# --- Perform LiftOver ---",
194
+ "lifted_grl <- rtracklayer::liftOver(gr, ch)",
195
+ "lifted_gr <- unlist(lifted_grl)",
196
+ "",
197
+ "# --- Export Lifted Data ---",
198
+ f"rtracklayer::export(lifted_gr, '{output_file.resolve()}')",
199
+ ]
200
+
201
+ if unmapped_output_file:
202
+ r_script_lines.extend([
203
+ "",
204
+ "# --- Handle Unmapped Regions ---",
205
+ "unmapped_indices <- elementNROWS(lifted_grl) == 0",
206
+ "unmapped_gr <- gr[unmapped_indices]",
207
+ f"if (length(unmapped_gr) > 0) {{",
208
+ f" rtracklayer::export(unmapped_gr, '{unmapped_output_file.resolve()}')",
209
+ f"}}",
210
+ ])
211
+
212
+ r_script_lines.append("cat('LiftOver successful.\\n')")
213
+ r_script = "\n".join(r_script_lines)
214
+
215
+ # 3. Execute R Script
216
+ with tempfile.NamedTemporaryFile(mode='w', suffix=".R", delete=False, dir=".") as tmp_script:
217
+ script_path = Path(tmp_script.name)
218
+ tmp_script.write(r_script)
219
+
220
+ command = ["Rscript", str(script_path.name)]
221
+ command_str = " ".join(command)
222
+
223
+ try:
224
+ result = subprocess.run(
225
+ command,
226
+ capture_output=True,
227
+ text=True,
228
+ check=True,
229
+ cwd=Path.cwd()
230
+ )
231
+
232
+ output_files = [str(output_file)]
233
+ if unmapped_output_file and unmapped_output_file.exists():
234
+ output_files.append(str(unmapped_output_file))
235
+
236
+ return {
237
+ "command_executed": command_str,
238
+ "stdout": result.stdout,
239
+ "stderr": result.stderr,
240
+ "output_files": output_files
241
+ }
242
+ except subprocess.CalledProcessError as e:
243
+ raise RuntimeError(
244
+ f"Rscript execution failed with return code {e.returncode}.\n"
245
+ f"Command: {command_str}\n"
246
+ f"Stderr: {e.stderr}\n"
247
+ f"Stdout: {e.stdout}"
248
+ ) from e
249
+ finally:
250
+ script_path.unlink()
251
+
252
+
253
+ @mcp_tool_placeholder()
254
+ def rtracklayer_ucsc_query(
255
+ output_file: Path,
256
+ genome: str,
257
+ track: str,
258
+ which: Optional[str] = None,
259
+ output_format: Optional[str] = None,
260
+ ):
261
+ """
262
+ Downloads data directly from the UCSC Table Browser.
263
+
264
+ This tool provides an interface to query and download genomic tracks from the
265
+ UCSC database, saving them to a local file in a specified format.
266
+
267
+ Args:
268
+ output_file: Path to write the downloaded data.
269
+ genome: The UCSC genome assembly to query (e.g., 'hg19', 'mm10').
270
+ track: The name of the track/table to download (e.g., 'knownGene', 'cpgIslandExt').
271
+ which: A specific genomic region to query, in the format 'chr:start-end'
272
+ (e.g., 'chr1:1000-2000'). If not provided, the whole genome is queried.
273
+ output_format: The format for the output file (e.g., 'bed', 'gff'). If not
274
+ provided, rtracklayer will guess from the file extension.
275
+ """
276
+ # 1. Input Validation
277
+ if which and not re.match(r"^\w+:\d+-\d+$", which):
278
+ raise ValueError("Invalid 'which' format. Expected 'chr:start-end' (e.g., 'chr1:1000-5000').")
279
+
280
+ # 2. Build R Script
281
+ r_script_lines = [
282
+ "suppressPackageStartupMessages(library(rtracklayer))",
283
+ "suppressPackageStartupMessages(library(GenomicRanges))",
284
+ "",
285
+ "# --- Build UCSC Data Source ---",
286
+ f"ucsc_args <- list(genome = '{genome}', tablename = '{track}')",
287
+ ]
288
+ if which:
289
+ r_script_lines.append(f"ucsc_args$range <- GRanges('{which}')")
290
+
291
+ r_script_lines.extend([
292
+ "ucsc_data_source <- do.call(UCSCData, ucsc_args)",
293
+ "",
294
+ "# --- Import Data from UCSC ---",
295
+ "gr_object <- rtracklayer::import(ucsc_data_source)",
296
+ "",
297
+ "# --- Export Data to File ---",
298
+ f"export_args <- list(object = gr_object, con = '{output_file.resolve()}')",
299
+ ])
300
+ if output_format:
301
+ r_script_lines.append(f"export_args$format <- '{output_format}'")
302
+
303
+ r_script_lines.extend([
304
+ "do.call(rtracklayer::export, export_args)",
305
+ "cat('UCSC data downloaded successfully.\\n')"
306
+ ])
307
+ r_script = "\n".join(r_script_lines)
308
+
309
+ # 3. Execute R Script
310
+ with tempfile.NamedTemporaryFile(mode='w', suffix=".R", delete=False, dir=".") as tmp_script:
311
+ script_path = Path(tmp_script.name)
312
+ tmp_script.write(r_script)
313
+
314
+ command = ["Rscript", str(script_path.name)]
315
+ command_str = " ".join(command)
316
+
317
+ try:
318
+ result = subprocess.run(
319
+ command,
320
+ capture_output=True,
321
+ text=True,
322
+ check=True,
323
+ cwd=Path.cwd()
324
+ )
325
+
326
+ return {
327
+ "command_executed": command_str,
328
+ "stdout": result.stdout,
329
+ "stderr": result.stderr,
330
+ "output_files": [str(output_file)]
331
+ }
332
+ except subprocess.CalledProcessError as e:
333
+ raise RuntimeError(
334
+ f"Rscript execution failed with return code {e.returncode}.\n"
335
+ f"Command: {command_str}\n"
336
+ f"Stderr: {e.stderr}\n"
337
+ f"Stdout: {e.stdout}"
338
+ ) from e
339
+ finally:
340
+ script_path.unlink()
Biomni/mcp_generated/mcp_bioconductor-scdotplot/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-scdotplot via conda (e.g., from bioconda)
14
+ RUN conda install -c bioconda bioconductor-scdotplot -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-scdotplot_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-scdotplot_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-scdotplot_server.py"]
40
+
Biomni/mcp_generated/mcp_bioconductor-scdotplot/app/bioconductor-scdotplot_server.py ADDED
@@ -0,0 +1,273 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import subprocess
2
+ import tempfile
3
+ import json
4
+ from pathlib import Path
5
+ from typing import List, Optional, Union
6
+
7
+ from mcp.server.fastmcp import FastMCP
8
+
9
+ SERVER_NAME = 'local_bioconductor_scdotplot'
10
+ mcp = FastMCP(SERVER_NAME)
11
+
12
+ @mcp.tool()
13
+ def sc_dot_plot(
14
+ input_path: str,
15
+ features: List[str],
16
+ group_by: str,
17
+ output_path: str = "dotplot.pdf",
18
+ cluster_cols: bool = True,
19
+ cluster_rows: bool = True,
20
+ assay: str = "RNA",
21
+ slot: str = "data",
22
+ scale: bool = True,
23
+ cap: float = 2.5,
24
+ annotation_cols: Optional[List[str]] = None,
25
+ annotation_rows: Optional[List[str]] = None,
26
+ ) -> dict:
27
+ """
28
+ Generate a clustered single-cell RNA-seq dot plot using the scDotPlot package.
29
+
30
+ Args:
31
+ input_path: Path to the input file (RDS format containing Seurat or SingleCellExperiment object).
32
+ features: List of gene names or features to plot.
33
+ group_by: Metadata column name to group cells by (e.g., 'ident' or 'cluster').
34
+ output_path: Path where the resulting plot (PDF, PNG, etc.) will be saved.
35
+ cluster_cols: Whether to perform hierarchical clustering on the columns (features).
36
+ cluster_rows: Whether to perform hierarchical clustering on the rows (cell groups).
37
+ assay: Name of the assay to use (for Seurat objects).
38
+ slot: Name of the slot to use (e.g., 'data', 'counts').
39
+ scale: Whether to scale the expression data.
40
+ cap: Value at which to cap the scaled expression data.
41
+ annotation_cols: Optional list of metadata columns to use for annotating the columns.
42
+ annotation_rows: Optional list of metadata columns to use for annotating the rows.
43
+ """
44
+ # Input validation
45
+ input_file = Path(input_path)
46
+ if not input_file.exists():
47
+ return {"error": f"Input file not found: {input_path}"}
48
+
49
+ output_file = Path(output_path)
50
+ output_file.parent.mkdir(parents=True, exist_ok=True)
51
+
52
+ # Prepare R script
53
+ features_r = ", ".join([f"'{f}'" for f in features])
54
+
55
+ # Handle optional annotations
56
+ ann_cols_r = "NULL"
57
+ if annotation_cols:
58
+ ann_cols_r = "c(" + ", ".join([f"'{a}'" for a in annotation_cols]) + ")"
59
+
60
+ ann_rows_r = "NULL"
61
+ if annotation_rows:
62
+ ann_rows_r = "c(" + ", ".join([f"'{a}'" for a in annotation_rows]) + ")"
63
+
64
+ r_script = f"""
65
+ library(scDotPlot)
66
+ library(Seurat)
67
+ library(SingleCellExperiment)
68
+ library(ggplot2)
69
+
70
+ # Load object
71
+ obj <- readRDS('{input_path}')
72
+
73
+ # Generate plot
74
+ p <- scDotPlot(
75
+ object = obj,
76
+ features = c({features_r}),
77
+ group.by = '{group_by}',
78
+ cluster.cols = {str(cluster_cols).upper()},
79
+ cluster.rows = {str(cluster_rows).upper()},
80
+ assay = '{assay}',
81
+ slot = '{slot}',
82
+ scale = {str(scale).upper()},
83
+ cap = {cap},
84
+ annotation_cols = {ann_cols_r},
85
+ annotation_rows = {ann_rows_r}
86
+ )
87
+
88
+ # Save plot
89
+ ggsave('{output_path}', plot = p)
90
+ """
91
+
92
+ with tempfile.NamedTemporaryFile(mode='w', suffix='.R', delete=False) as tmp:
93
+ tmp.write(r_script)
94
+ tmp_path = tmp.name
95
+
96
+ try:
97
+ result = subprocess.run(
98
+ ["Rscript", tmp_path],
99
+ capture_output=True,
100
+ text=True,
101
+ check=True
102
+ )
103
+ return {
104
+ "command_executed": f"scDotPlot on {input_path}",
105
+ "stdout": result.stdout,
106
+ "stderr": result.stderr,
107
+ "output_files": [str(output_file.absolute())]
108
+ }
109
+ except subprocess.CalledProcessError as e:
110
+ return {
111
+ "error": "R execution failed",
112
+ "stdout": e.stdout,
113
+ "stderr": e.stderr,
114
+ "command_executed": f"Rscript {tmp_path}"
115
+ }
116
+ finally:
117
+ Path(tmp_path).unlink(missing_ok=True)
118
+
119
+ @mcp.tool()
120
+ def sc_dot_plot_data(
121
+ input_path: str,
122
+ features: List[str],
123
+ group_by: str,
124
+ output_csv: str = "dotplot_data.csv",
125
+ assay: str = "RNA",
126
+ slot: str = "data",
127
+ scale: bool = True,
128
+ cap: float = 2.5,
129
+ ) -> dict:
130
+ """
131
+ Extract the underlying data frame used for generating a scDotPlot.
132
+
133
+ Args:
134
+ input_path: Path to the input file (RDS format).
135
+ features: List of gene names or features.
136
+ group_by: Metadata column name to group cells by.
137
+ output_csv: Path to save the extracted data as a CSV file.
138
+ assay: Name of the assay to use.
139
+ slot: Name of the slot to use.
140
+ scale: Whether to scale the expression data.
141
+ cap: Value at which to cap the scaled expression data.
142
+ """
143
+ input_file = Path(input_path)
144
+ if not input_file.exists():
145
+ return {"error": f"Input file not found: {input_path}"}
146
+
147
+ features_r = ", ".join([f"'{f}'" for f in features])
148
+
149
+ r_script = f"""
150
+ library(scDotPlot)
151
+ library(Seurat)
152
+ library(SingleCellExperiment)
153
+
154
+ obj <- readRDS('{input_path}')
155
+
156
+ # Extract data using the internal data preparation logic
157
+ plot_data <- scDotPlotData(
158
+ object = obj,
159
+ features = c({features_r}),
160
+ group.by = '{group_by}',
161
+ assay = '{assay}',
162
+ slot = '{slot}',
163
+ scale = {str(scale).upper()},
164
+ cap = {cap}
165
+ )
166
+
167
+ write.csv(plot_data, '{output_csv}', row.names = FALSE)
168
+ """
169
+
170
+ with tempfile.NamedTemporaryFile(mode='w', suffix='.R', delete=False) as tmp:
171
+ tmp.write(r_script)
172
+ tmp_path = tmp.name
173
+
174
+ try:
175
+ result = subprocess.run(
176
+ ["Rscript", tmp_path],
177
+ capture_output=True,
178
+ text=True,
179
+ check=True
180
+ )
181
+ return {
182
+ "command_executed": f"scDotPlotData on {input_path}",
183
+ "stdout": result.stdout,
184
+ "stderr": result.stderr,
185
+ "output_files": [str(Path(output_csv).absolute())]
186
+ }
187
+ except subprocess.CalledProcessError as e:
188
+ return {
189
+ "error": "R execution failed",
190
+ "stdout": e.stdout,
191
+ "stderr": e.stderr
192
+ }
193
+ finally:
194
+ Path(tmp_path).unlink(missing_ok=True)
195
+
196
+ @mcp.tool()
197
+ def sc_dot_plot_custom_df(
198
+ data_path: str,
199
+ features_col: str,
200
+ groups_col: str,
201
+ avg_exp_col: str,
202
+ pct_exp_col: str,
203
+ output_path: str = "dotplot_from_df.pdf",
204
+ cluster_cols: bool = True,
205
+ cluster_rows: bool = True,
206
+ ) -> dict:
207
+ """
208
+ Generate a clustered dot plot from a pre-calculated data frame instead of a Seurat/SCE object.
209
+
210
+ Args:
211
+ data_path: Path to a CSV file containing the summarized expression data.
212
+ features_col: Column name in the CSV containing gene/feature names.
213
+ groups_col: Column name in the CSV containing cell group/cluster names.
214
+ avg_exp_col: Column name in the CSV containing average expression values.
215
+ pct_exp_col: Column name in the CSV containing percent of cells expressing the gene.
216
+ output_path: Path where the resulting plot will be saved.
217
+ cluster_cols: Whether to perform hierarchical clustering on columns.
218
+ cluster_rows: Whether to perform hierarchical clustering on rows.
219
+ """
220
+ data_file = Path(data_path)
221
+ if not data_file.exists():
222
+ return {"error": f"Data file not found: {data_path}"}
223
+
224
+ r_script = f"""
225
+ library(scDotPlot)
226
+ library(ggplot2)
227
+
228
+ # Load data frame
229
+ df <- read.csv('{data_path}')
230
+
231
+ # Generate plot using the data frame method
232
+ # Note: scDotPlot handles data frames by mapping columns
233
+ p <- scDotPlot(
234
+ object = df,
235
+ features = '{features_col}',
236
+ group.by = '{groups_col}',
237
+ avg.exp = '{avg_exp_col}',
238
+ pct.exp = '{pct_exp_col}',
239
+ cluster.cols = {str(cluster_cols).upper()},
240
+ cluster.rows = {str(cluster_rows).upper()}
241
+ )
242
+
243
+ ggsave('{output_path}', plot = p)
244
+ """
245
+
246
+ with tempfile.NamedTemporaryFile(mode='w', suffix='.R', delete=False) as tmp:
247
+ tmp.write(r_script)
248
+ tmp_path = tmp.name
249
+
250
+ try:
251
+ result = subprocess.run(
252
+ ["Rscript", tmp_path],
253
+ capture_output=True,
254
+ text=True,
255
+ check=True
256
+ )
257
+ return {
258
+ "command_executed": f"scDotPlot on data frame {data_path}",
259
+ "stdout": result.stdout,
260
+ "stderr": result.stderr,
261
+ "output_files": [str(Path(output_path).absolute())]
262
+ }
263
+ except subprocess.CalledProcessError as e:
264
+ return {
265
+ "error": "R execution failed",
266
+ "stdout": e.stdout,
267
+ "stderr": e.stderr
268
+ }
269
+ finally:
270
+ Path(tmp_path).unlink(missing_ok=True)
271
+
272
+ if __name__ == "__main__":
273
+ mcp.run(transport="stdio")
Biomni/mcp_generated/mcp_bioconductor-scdotplot/app/bioconductor-scdotplot_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-scdotplot/app/bioconductor-scdotplot_server.py')
11
+ LOCAL_SERVER = Path(__file__).with_name(SOURCE_SERVER.name)
12
+ SERVER_NAME = 'biosci_bioconductor_scdotplot'
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-scdotplot/app/requirements.txt ADDED
@@ -0,0 +1 @@
 
 
1
+
Biomni/mcp_generated/mcp_bioconductor-scdotplot/docker-compose.yml ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ version: '3.8'
2
+
3
+ services:
4
+ mcp-bioconductor-scdotplot:
5
+ build: .
6
+ image: mcp-bioconductor-scdotplot:latest
7
+ container_name: mcp-bioconductor-scdotplot
8
+ ports:
9
+ - "8000:8000"
10
+ environment:
11
+ - MCP_SERVER_NAME=bioconductor-scdotplot
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
+