Skip to content

Commit dc2ad94

Browse files
authored
Merge pull request #42 from HolobiomicsLab/manuscript_alignment_and_minor_fix_in_deploy
Manuscript alignment and minor fix in deploy
2 parents 237660a + beb7751 commit dc2ad94

5 files changed

Lines changed: 129 additions & 66 deletions

File tree

README.md

Lines changed: 93 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<h1 align="center">Toolomics</h1>
22

33
<p align="center">
4-
<em>A suite of MCP-based Tools from the HolobiomicsLab. Used by AI-Agents such as **Mimosa-AI**</em>
4+
<em>Companion platform for MCP server management and workspace-isolated scientific tool execution for Mimosa and other MCP-compatible agents.</em>
55
</p>
66

77
<p align="center">
@@ -21,52 +21,73 @@
2121

2222
---
2323

24-
> ***Toolomics*** — deploys containerized tools, manages isolated instances, and enables file sharing across AI agents for bioinformatics, metabolomics, molecular docking, and beyond.
24+
> ***Toolomics*** exposes computational tools as discoverable MCP services, manages isolated multi-instance workspaces, and lets agents share files across scientific workflows.
2525
26-
**Use cases:**
27-
- Deploy MCP servers for browser automation, PDF processing, and data extraction
28-
- Run isolated, multi-instance agent workspaces with automatic resource management
29-
- Orchestrate containerized bioinformatics pipelines (XCMS, RStudio, Redis) with zero config
26+
In this repository, Toolomics:
27+
- discovering MCP services from `server.py` and `docker-compose.yml` definitions under `mcp_host/`
28+
- assigning ports and recording them in instance-specific `config_<instance_id>.json` files
29+
- isolating workspaces, Docker projects, volumes, and auxiliary services per deployment instance
30+
- making files created by one MCP server immediately available to other MCP servers through a shared workspace
3031

31-
## Install & deploy tools
32+
## Quick Start
3233

33-
### Deploy all tools automatically
34+
Run Toolomics against a workspace and a port range:
3435

3536
```bash
3637
./start.sh <min port> <max port> <workspace name>
3738
```
3839

39-
### Deploy using python script
40+
Example:
4041

41-
***Not recommanded, start.sh will handle python, requirements and workpsace installation automatically.***
42+
```bash
43+
./start.sh 5000 5099 workspace_mimosa
44+
```
45+
46+
On first run, Toolomics will:
47+
1. check Python and `pip`
48+
2. optionally install `requirements.txt`
49+
3. create or reuse the requested workspace
50+
4. derive an instance ID from the workspace path
51+
5. create or update `config_<instance_id>.json` with discovered services and assigned ports
4252

43-
First, install the required dependencies, you can use either pip or the faster UV package manager:
53+
Newly discovered services are added with `"enabled": false` by default. Enable the MCP servers you want in the generated config file, then rerun `./start.sh`.
4454

45-
**1. Install dependencies:**
55+
### Manual Deployment
56+
57+
If you prefer to run the deployment script directly:
58+
59+
**1. Install dependencies**
4660
```bash
4761
python3.10 -m pip install -r requirements.txt
48-
# or using UV
62+
# or
4963
uv pip install -r requirements.txt
5064
```
5165

52-
**2. Run script:**
66+
**2. Run the deployment manager**
5367
```bash
54-
python3.10 deploy.py --config config.json --workspace <workspace name> --host_port_min <min port> --host_port_max <max port>
68+
python3.10 deploy.py --config config.json --mcp-dir mcp_host --workspace <workspace name> --host_port_min <min port> --host_port_max <max port>
5569
```
5670

57-
## Centralized File Management
71+
Passing `--config config.json` is supported, but `deploy.py` will automatically expand it to an instance-specific file such as `config_86517947.json` based on the workspace path.
72+
73+
## Centralized Workspace
5874

59-
All MCP servers execute in a centralized **workspace directory** (default: `workspace/`). This means:
75+
All MCP servers execute against a centralized workspace directory (default: `workspace/`). This means:
6076

61-
- **Browser MCP** downloads files → `workspace/downloaded_file.pdf`
62-
- **PDF MCP** processes files → `workspace/extracted_text.txt`
63-
- **Any MCP** creates files → `workspace/output_file.json`
77+
- Browser MCP downloads files to the workspace
78+
- PDF MCP processes files already present in the workspace
79+
- Other MCP servers can consume the same files without copying them between tool-specific directories
80+
81+
Example paths:
82+
- `workspace/downloaded_file.pdf`
83+
- `workspace/extracted_text.txt`
84+
- `workspace/output_file.json`
6485

6586
This centralized approach ensures that AI agents can easily find and work with files across different MCP tools without needing to track file locations.
6687

6788
## Multi-Instance Deployment
6889

69-
Toolomics supports running **multiple independent instances simultaneously**, each with its own workspace and Docker service isolation.
90+
Toolomics supports running multiple independent instances simultaneously, each with its own workspace and Docker service isolation.
7091

7192
### How It Works
7293

@@ -76,14 +97,14 @@ Each instance is automatically assigned a unique **instance ID** (8-character ha
7697

7798
This means each instance has its own configuration and doesn't interfere with others.
7899

79-
**Example: Deploy two instances concurrently**
100+
**Example: deploy two instances concurrently**
80101

81102
```bash
82103
# Terminal 1: Instance for user Martin
83-
start.sh 5000 5100 workspace_martin
104+
./start.sh 5000 5099 workspace_martin
84105

85106
# Terminal 2: Instance for user John (simultaneous)
86-
start.sh 5100 5200 workspace_john
107+
./start.sh 5100 5199 workspace_john
87108
```
88109

89110
### Automatic Resource Isolation
@@ -95,31 +116,49 @@ Each instance automatically gets isolated resources:
95116
| **Workspace** | Separate directory (`workspace_martin/`, `workspace_john/`) |
96117
| **Docker Containers** | Suffixed with instance ID (`xcmsrocker_a3f2b1c9`, `xcmsrocker_f7e2d4a1`) |
97118
| **Data Volumes** | Instance-specific names (`rstudio_data_a3f2b1c9`, `redis-data_f7e2d4a1`) |
98-
| **MCP Server Ports** | Different port ranges (5000-5100 vs 5100-5200) |
119+
| **MCP Server Ports** | Different port ranges (5000-5099 vs 5100-5199) |
99120
| **Auxiliary Ports** | Dynamic allocation (8787→9537, 8080→9037, etc.) |
100121

101-
## Using MCP with Your Client
122+
This multi-tenant, workspace-isolated design is the same property referenced in the manuscript when Toolomics is described as the companion discovery and execution layer for Mimosa.
123+
124+
## Discovering And Using MCP Services
102125

103-
To interact with the tools using a client (e.g., for your AI agent), you can use the `fastmcp` library.
126+
To interact with the tools using a client such as Mimosa or another MCP-compatible agent, you can use the generated config file directly or scan a predefined local port range.
104127

105128
### Finding the MCP Port
106129

107-
Each MCP server is assigned a port, which is recorded in the `config.json` file. For example:
130+
Each MCP server is assigned a port, which is recorded in the instance-specific config file. For example:
108131

109132
```json
110133
[
111-
{
112-
"mcp_host/browser/server.py": 5002
113-
},
114-
{
115-
"mcp_host/Rscript/server.py": 5001
116-
},
117-
{
118-
"mcp_host/files/csv/server.py": 5101
119-
}
134+
{
135+
"path": "mcp_host/pdf/server.py",
136+
"port": 5002,
137+
"enabled": true
138+
},
139+
{
140+
"path": "mcp_host/image_analysis/server.py",
141+
"port": 5006,
142+
"enabled": true
143+
},
144+
{
145+
"path": "mcp_host/shell/docker-compose.yml",
146+
"port": 5012,
147+
"enabled": true
148+
}
120149
]
121150
```
122151

152+
### Scanning A Predefined Port Range
153+
154+
Toolomics includes a helper script that scans `localhost:5000-5200` and enumerates active MCP tools:
155+
156+
```bash
157+
python3 discover_mcp.py
158+
```
159+
160+
This mirrors the local port-range discovery pattern described in the manuscript for Mimosa's tool discovery layer.
161+
123162
### Example Client Code
124163

125164
Here is an example of how to use a client to interact with an MCP server running on port `5002`:
@@ -146,17 +185,16 @@ async def main():
146185
# Other MCP tools can access it from the same location
147186
```
148187

149-
## Adding a New MCP
188+
## Adding A New MCP
150189

151190
You can easily add a new tool as an MCP server.
152191

153192
### Steps to Add a New MCP
154193

155-
1. Create a `server.py` file with your MCP implementation, it should take the port number as first argument (eg: `server.py 5003`).
156-
2. Place the file in a subfolder of the `mcp_host` directory. For example, to add a metabolomics-related tool, create a subfolder like `mcp_host/your_tool_name`.
157-
158-
The `deploy.py` script will look for new `server.py` file, attribute a port for your script and add it to `config.json` (unless you manually did by modifying the config.json), finally it will run your script with the assigned port as first argument.
159-
194+
1. Create a `server.py` file with your MCP implementation. It should accept the assigned port as either an environment variable or the first command-line argument.
195+
2. Place the file in a subfolder of `mcp_host/`, for example `mcp_host/your_tool_name/server.py`.
196+
3. Run `./start.sh` or `deploy.py` to let Toolomics discover the service and assign it a port.
197+
4. Set `"enabled": true` for the new service in the generated `config_<instance_id>.json`, then rerun deployment.
160198

161199
### Example MCP Implementation
162200

@@ -165,16 +203,17 @@ The `fastmcp` library simplifies the creation of MCP servers. Here's a basic exa
165203
```python
166204
#!/usr/bin/env python3
167205

168-
from fastmcp import FastMCP
206+
import os
207+
import sys
169208
from pathlib import Path
209+
from fastmcp import FastMCP
210+
170211
project_root = Path(__file__).resolve().parent.parent.parent
171-
sys.path.append(str(project_root)) # Add 'a/' to Python's search path
212+
sys.path.append(str(project_root))
172213

173214
from shared import CommandResult, run_bash_subprocess, return_as_dict
174215

175-
description = """
176-
a calculator that ...
177-
"""
216+
description = "A calculator MCP."
178217

179218
mcp = FastMCP(
180219
name="calculator",
@@ -200,20 +239,17 @@ if __name__ == "__main__":
200239
mcp.run(transport="streamable-http", port=port, host="0.0.0.0")
201240
```
202241

203-
### Automatic Port Assignment
204-
205-
When you run the `start.sh` or `deploy.py` script for the first time, it will automatically assign a port to your new MCP server and save the mapping in the `config.json` file.
206-
207242
## Dockerizing an MCP Server
208243

209-
For MCP servers that require isolated dependencies or need to run in a containerized environment (e.g., for ML models, system tools, or heavy dependencies), you can deploy them using Docker.
244+
For MCP servers that require isolated dependencies or need to run in a containerized environment, you can deploy them with Docker. This is one of the main ways Toolomics keeps tool dependencies isolated across concurrent scientific workflows.
210245

211246
### How It Works
212247

213248
If a `docker-compose.yml` file exists in the same directory as your `server.py`, the deployment script will:
214249
- **Automatically deploy the server in Docker** instead of running it directly on the host
215250
- **Skip the standalone Python execution** to avoid duplicate deployments
216251
- **Pass the assigned port** to the Docker container via the `MCP_PORT` environment variable
252+
- **Pass instance isolation metadata** such as `INSTANCE_ID` and `WORKSPACE_PATH` to the container
217253

218254
### Steps to Dockerize an MCP
219255

@@ -276,8 +312,9 @@ services:
276312
- "${MCP_PORT}:${MCP_PORT}"
277313
environment:
278314
- MCP_PORT=${MCP_PORT}
315+
- INSTANCE_ID=${INSTANCE_ID}
279316
volumes:
280-
- ../../workspace:/workspace
317+
- ../../${WORKSPACE_PATH}:/app/workspace:rw
281318
```
282319
283320
**Important**: The build context must be set to the project root (`../..`) to allow the Dockerfile to access `shared.py` and other project files.
@@ -286,8 +323,9 @@ services:
286323

287324
The deployment script will automatically:
288325
- Detect the `docker-compose.yml`
289-
- Assign a port (5000-5099 range for mcp_host)
326+
- Assign a port in the selected host range
290327
- Set the `MCP_PORT` environment variable
328+
- Set `INSTANCE_ID` and `WORKSPACE_PATH`
291329
- Build and start the Docker container
292330
- Skip running `server.py` directly on the host
293331

deploy.py

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -510,7 +510,7 @@ def load_config(self) -> Dict[str, dict]:
510510
'enabled': item.get('enabled', True) # Default to enabled
511511
}
512512
else:
513-
raise ValueError("Can't parse config.json file")
513+
raise ValueError(f"Can't parse config file: {self.config_path}")
514514

515515
logger.info(f"Successfully loaded {len(config_dict)} items from config (enabled: {sum(1 for v in config_dict.values() if v.get('enabled'))})")
516516
return config_dict
@@ -620,7 +620,10 @@ def assign_ports(self, server_files: List[Path], compose_files: List[Path] = Non
620620

621621
config[server_str] = {'port': next_host_port, 'enabled': False}
622622
used_ports.add(next_host_port)
623-
logger.info(f"Assigned host port {next_host_port} to {server_str} (disabled - edit config to enable)")
623+
logger.info(
624+
f"Assigned host port {next_host_port} to {server_str} "
625+
f"(disabled - edit {self.config_path} to enable)"
626+
)
624627
next_host_port += 1
625628

626629
# Assign ports to docker-compose files
@@ -638,7 +641,10 @@ def assign_ports(self, server_files: List[Path], compose_files: List[Path] = Non
638641

639642
config[compose_str] = {'port': next_host_port, 'enabled': False}
640643
used_ports.add(next_host_port)
641-
logger.info(f"Assigned host port {next_host_port} to {compose_str} (disabled - edit config to enable)")
644+
logger.info(
645+
f"Assigned host port {next_host_port} to {compose_str} "
646+
f"(disabled - edit {self.config_path} to enable)"
647+
)
642648
next_host_port += 1
643649

644650
self.save_config(config)
@@ -751,15 +757,18 @@ def _deploy_docker_services(self, compose_files: List[Path], port_config: Dict[s
751757

752758
# Save config if any ports were reassigned
753759
if config_updated:
754-
logger.info("Updating config.json with new port assignments")
760+
logger.info(f"Updating {self.config_manager.config_path} with new port assignments")
755761
self.config_manager.save_config(port_config)
756762

757763
if started_count > 0:
758764
logger.info(f"Started {started_count} Docker services ({disabled_count} disabled)")
759765
logger.info("Waiting for Docker services to start...")
760766
time.sleep(3)
761767
elif disabled_count > 0:
762-
logger.info(f"⚠️ All {disabled_count} Docker services are disabled. Change config.json to enable.")
768+
logger.info(
769+
f"⚠️ All {disabled_count} Docker services are disabled. "
770+
f"Change {self.config_manager.config_path} to enable them."
771+
)
763772

764773
def _deploy_mcp_servers(self, server_files: List[Path], port_config: Dict[str, dict],
765774
host_port_min: int = HOST_PORT_MIN, host_port_max: int = HOST_PORT_MAX):
@@ -811,12 +820,15 @@ def _deploy_mcp_servers(self, server_files: List[Path], port_config: Dict[str, d
811820

812821
# Save config if any ports were reassigned
813822
if config_updated:
814-
logger.info("Updating config.json with new port assignments")
823+
logger.info(f"Updating {self.config_manager.config_path} with new port assignments")
815824
self.config_manager.save_config(port_config)
816825

817826
logger.info(f"Started {started_count} MCP servers ({disabled_count} disabled)")
818827
if started_count == 0:
819-
raise Exception("⚠️ No MCP server enabled, change config.json and select MCP servers to enable.")
828+
raise Exception(
829+
f"⚠️ No MCP server enabled. Change {self.config_manager.config_path} "
830+
"and select MCP servers to enable."
831+
)
820832

821833
def main():
822834
parser = argparse.ArgumentParser(description="Deploy MCP servers with centralized workspace file management")
@@ -852,4 +864,4 @@ def main():
852864
sys.exit(1)
853865

854866
if __name__ == "__main__":
855-
main()
867+
main()

discover_mcp.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from fastmcp import Client
88

99
async def discover_mcp_servers():
10-
"""Discover MCP servers on ports 5000-5050 and list their tools."""
10+
"""Discover MCP servers on ports 5000-5200 and list their tools."""
1111
print("🔍 Discovering MCP servers on ports 5000-5200...")
1212

1313
for port in range(5000, 5201):
@@ -27,4 +27,4 @@ async def discover_mcp_servers():
2727

2828
if __name__ == "__main__":
2929
print("🧪 Starting MCP Server Discovery")
30-
asyncio.run(discover_mcp_servers())
30+
asyncio.run(discover_mcp_servers())

docs/licensing-notes.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,6 @@ This wording is intended for repository governance documentation and remains sub
2525

2626
## Co-authorship context (informational)
2727

28-
Current named research co-authors: Martin Legrand, Tao Jiang, Matthieu Feraud, Benjamin Navet, and Louis-Felix Nothias.
28+
Current named manuscript co-authors: Martin Legrand, Tao Jiang, Matthieu Feraud, Benjamin Navet, Yousouf Taghzouti, Fabien Gandon, Elise Dumont, and Louis-Felix Nothias.
2929

3030
This co-authorship note is informational and does not itself determine legal ownership or licensing authority.

start.sh

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,19 @@ echo "Deploying MCP servers..."
194194
$PYTHON deploy.py --config config.json --mcp-dir mcp_host --host_port_min "$START_PORT" --host_port_max "$END_PORT" --workspace $WORKSPACE &
195195
HOST_PID=$!
196196
wait $HOST_PID
197+
DEPLOY_EXIT_CODE=$?
198+
199+
echo ""
200+
if [ $DEPLOY_EXIT_CODE -ne 0 ]; then
201+
echo "=== DEPLOYMENT FAILED ==="
202+
echo "deploy.py exited with status $DEPLOY_EXIT_CODE"
203+
echo ""
204+
echo "Instance-specific config file: $INSTANCE_CONFIG"
205+
echo "If this was a first run, enable the MCP services you want in that file"
206+
echo "and rerun this command."
207+
echo ""
208+
exit $DEPLOY_EXIT_CODE
209+
fi
197210

198211
# After deployment, show the config file location
199212
echo ""

0 commit comments

Comments
 (0)