ghh1125 commited on
Commit
0f7ff0b
·
verified ·
1 Parent(s): 96bb415

Upload 14 files

Browse files
Dockerfile CHANGED
@@ -1,18 +1,23 @@
1
- FROM python:3.10
2
 
3
- RUN useradd -m -u 1000 user && python -m pip install --upgrade pip
4
- USER user
5
- ENV PATH="/home/user/.local/bin:$PATH"
 
6
 
7
  WORKDIR /app
8
 
9
- COPY --chown=user ./requirements.txt requirements.txt
10
- RUN pip install --no-cache-dir --upgrade -r requirements.txt
 
 
 
11
 
12
- COPY --chown=user . /app
13
  ENV MCP_TRANSPORT=http
14
  ENV MCP_PORT=7860
15
 
16
  EXPOSE 7860
17
 
 
 
18
  CMD ["python", "deepchem/mcp_output/start_mcp.py"]
 
1
+ FROM python:3.11-slim
2
 
3
+ ENV PYTHONDONTWRITEBYTECODE=1
4
+ ENV PYTHONUNBUFFERED=1
5
+
6
+ RUN useradd -m -u 1000 appuser
7
 
8
  WORKDIR /app
9
 
10
+ COPY requirements.txt /app/requirements.txt
11
+ RUN pip install --no-cache-dir -r /app/requirements.txt
12
+
13
+ COPY deepchem /app/deepchem
14
+ COPY app.py /app/app.py
15
 
 
16
  ENV MCP_TRANSPORT=http
17
  ENV MCP_PORT=7860
18
 
19
  EXPOSE 7860
20
 
21
+ USER appuser
22
+
23
  CMD ["python", "deepchem/mcp_output/start_mcp.py"]
README.md CHANGED
@@ -1,10 +1,57 @@
1
  ---
2
- title: Deepchem
3
- emoji: 📈
4
- colorFrom: purple
5
- colorTo: purple
6
  sdk: docker
7
  pinned: false
 
8
  ---
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ title: deepchem MCP Service
3
+ emoji: 🔧
4
+ colorFrom: blue
5
+ colorTo: indigo
6
  sdk: docker
7
  pinned: false
8
+ license: mit
9
  ---
10
 
11
+ # deepchem MCP Service
12
+
13
+ This deployment packages a FastMCP wrapper around the DeepChem Python repository and exposes practical tools for molecular featurization, dataset loading, split generation, and metrics.
14
+
15
+ ## Available Tools
16
+
17
+ - `health_check`
18
+ - `list_modules`
19
+ - `list_symbols`
20
+ - `featurize_smiles`
21
+ - `split_indices`
22
+ - `classification_metrics`
23
+ - `load_molnet_dataset`
24
+
25
+ See detailed parameter and usage docs in `deepchem/mcp_output/README_MCP.md`.
26
+
27
+ ## Local stdio (Claude Desktop / CLI)
28
+
29
+ ```bash
30
+ cd deepchem/mcp_output
31
+ python start_mcp.py
32
+ ```
33
+
34
+ Or run plugin entry directly:
35
+
36
+ ```bash
37
+ cd deepchem/mcp_output/mcp_plugin
38
+ python main.py
39
+ ```
40
+
41
+ ## HTTP MCP (Docker / remote)
42
+
43
+ ```bash
44
+ MCP_TRANSPORT=http MCP_PORT=8000 python deepchem/mcp_output/start_mcp.py
45
+ ```
46
+
47
+ MCP endpoint:
48
+
49
+ - `http://localhost:8000/mcp`
50
+
51
+ ## HuggingFace Spaces (Docker)
52
+
53
+ This package is configured for HF Spaces Docker runtime:
54
+
55
+ - service port: `7860`
56
+ - Docker entrypoint: `python deepchem/mcp_output/start_mcp.py`
57
+ - HTTP MCP endpoint: `/mcp`
app.py CHANGED
@@ -1,45 +1,90 @@
1
- from fastapi import FastAPI
 
2
  import os
3
  import sys
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4
 
5
- mcp_plugin_path = os.path.join(os.path.dirname(__file__), "deepchem", "mcp_output", "mcp_plugin")
6
- sys.path.insert(0, mcp_plugin_path)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7
 
8
- app = FastAPI(
9
- title="Deepchem MCP Service",
10
- description="Auto-generated MCP service for deepchem",
11
- version="1.0.0"
12
- )
13
 
14
  @app.get("/")
15
- def root():
16
  return {
17
- "service": "Deepchem MCP Service",
18
- "version": "1.0.0",
19
- "status": "running",
20
- "transport": os.environ.get("MCP_TRANSPORT", "http")
 
21
  }
22
 
 
23
  @app.get("/health")
24
- def health_check():
25
- return {"status": "healthy", "service": "deepchem MCP"}
 
26
 
27
  @app.get("/tools")
28
- def list_tools():
29
  try:
30
- from mcp_service import create_app
31
- mcp_app = create_app()
32
- tools = []
33
- for tool_name, tool_func in mcp_app.tools.items():
34
- tools.append({
35
- "name": tool_name,
36
- "description": tool_func.__doc__ or "No description available"
37
- })
38
- return {"tools": tools}
39
- except Exception as e:
40
- return {"error": f"Failed to load tools: {str(e)}"}
41
-
42
- if __name__ == "__main__":
43
- import uvicorn
44
- port = int(os.environ.get("PORT", 7860))
45
- uvicorn.run(app, host="0.0.0.0", port=port)
 
1
+ from __future__ import annotations
2
+
3
  import os
4
  import sys
5
+ import importlib
6
+ from pathlib import Path
7
+ from typing import Any, Dict, List
8
+
9
+ try:
10
+ FastAPI = importlib.import_module("fastapi").FastAPI
11
+ except Exception:
12
+ FastAPI = None
13
+
14
+ PLUGIN_DIR = (Path(__file__).resolve().parent / "deepchem" / "mcp_output" / "mcp_plugin").resolve()
15
+ if str(PLUGIN_DIR) not in sys.path:
16
+ sys.path.insert(0, str(PLUGIN_DIR))
17
+
18
+ if FastAPI is None:
19
+ raise RuntimeError("fastapi is required to run app.py. Install dependencies from requirements.txt")
20
+
21
+ app = FastAPI(title="DeepChem MCP Info API", version="1.0.0")
22
+
23
 
24
+ def _extract_tools(mcp_obj: Any) -> List[Dict[str, str]]:
25
+ tools: List[Dict[str, str]] = []
26
+
27
+ direct_tools = getattr(mcp_obj, "tools", None)
28
+ if isinstance(direct_tools, dict):
29
+ for key, tool_obj in direct_tools.items():
30
+ name = getattr(tool_obj, "name", None) or str(key)
31
+ description = getattr(tool_obj, "description", "")
32
+ tools.append({"name": str(name), "description": str(description)})
33
+ elif isinstance(direct_tools, (list, tuple, set)):
34
+ for tool_obj in direct_tools:
35
+ name = getattr(tool_obj, "name", None) or getattr(tool_obj, "__name__", "unknown")
36
+ description = getattr(tool_obj, "description", "") or getattr(tool_obj, "__doc__", "") or ""
37
+ tools.append({"name": str(name), "description": str(description).strip()})
38
+
39
+ if not tools:
40
+ list_tools_fn = getattr(mcp_obj, "list_tools", None)
41
+ if callable(list_tools_fn):
42
+ try:
43
+ listed = list_tools_fn()
44
+ if isinstance(listed, (list, tuple)):
45
+ for tool_obj in listed:
46
+ if isinstance(tool_obj, dict):
47
+ tools.append(
48
+ {
49
+ "name": str(tool_obj.get("name", "unknown")),
50
+ "description": str(tool_obj.get("description", "")),
51
+ }
52
+ )
53
+ else:
54
+ name = getattr(tool_obj, "name", None) or getattr(tool_obj, "__name__", "unknown")
55
+ description = getattr(tool_obj, "description", "")
56
+ tools.append({"name": str(name), "description": str(description)})
57
+ except Exception:
58
+ pass
59
+
60
+ dedup: Dict[str, Dict[str, str]] = {}
61
+ for item in tools:
62
+ dedup[item["name"]] = item
63
+ return list(dedup.values())
64
 
 
 
 
 
 
65
 
66
  @app.get("/")
67
+ def info() -> Dict[str, Any]:
68
  return {
69
+ "service": "deepchem-mcp",
70
+ "description": "Supplementary FastAPI info service for the DeepChem MCP deployment.",
71
+ "mcp_endpoint": "/mcp",
72
+ "transport_env": os.getenv("MCP_TRANSPORT", "stdio"),
73
+ "default_port": int(os.getenv("PORT", "7860")),
74
  }
75
 
76
+
77
  @app.get("/health")
78
+ def health() -> Dict[str, str]:
79
+ return {"status": "healthy"}
80
+
81
 
82
  @app.get("/tools")
83
+ def tools() -> Dict[str, Any]:
84
  try:
85
+ create_app = importlib.import_module("mcp_service").create_app
86
+
87
+ mcp_obj = create_app()
88
+ return {"status": "ok", "tools": _extract_tools(mcp_obj)}
89
+ except Exception as exc:
90
+ return {"status": "error", "tools": [], "error": str(exc)}
 
 
 
 
 
 
 
 
 
 
deepchem/mcp_output/README_MCP.md CHANGED
@@ -1,118 +1,149 @@
1
- # DeepChem MCP (Model Context Protocol) Service README
2
-
3
- ## 1) Project Introduction
4
-
5
- This MCP (Model Context Protocol) service wraps core DeepChem capabilities for molecular ML workflows, including:
6
-
7
- - Dataset loading and preprocessing (CSV/SDF/JSON/image/biological sequence)
8
- - Featurization (ECFP, graph, SMILES/token, protein/complex features)
9
- - Model training/inference (Torch, TensorFlow, JAX, sklearn, XGBoost)
10
- - Benchmarking via MolNet loaders and benchmark runners
11
- - Evaluation, metrics, splitting, transforms, and utility workflows
12
-
13
- Repository: https://github.com/deepchem/deepchem
14
-
15
- ---
16
-
17
- ## 2) Installation
18
-
19
- ### Base requirements
20
- - Python 3.8+ (recommended 3.10/3.11)
21
- - Core libs: `numpy scipy pandas scikit-learn joblib tqdm`
22
-
23
- Install core package:
24
- - `pip install deepchem`
25
-
26
- Install optional stacks as needed:
27
- - Chemistry: `rdkit`
28
- - Deep learning: `torch` or `tensorflow` or `jax`
29
- - Extras: `pytorch-lightning transformers dgl torch-geometric xgboost lightgbm pyscf`
30
-
31
- Practical setup pattern:
32
- 1. Create a fresh virtual environment
33
- 2. Install `deepchem`
34
- 3. Add only the backend/features you need (Torch/TensorFlow/JAX + RDKit)
35
-
36
- ---
37
-
38
- ## 3) Quick Start
39
-
40
- ### Typical MCP (Model Context Protocol) flow
41
- 1. Load dataset from MolNet (e.g., Tox21, Delaney, QM9)
42
- 2. Apply featurizer and split strategy
43
- 3. Train selected model
44
- 4. Evaluate with DeepChem metrics
45
- 5. Return scores/predictions via MCP (Model Context Protocol) response
46
-
47
- ### Example task patterns
48
- - Classification benchmark: use `deepchem.molnet.run_benchmark`
49
- - Low-data benchmark: use `deepchem.molnet.run_benchmark_low_data`
50
- - Programmatic loading: use `deepchem.molnet.load_function.*`
51
- - Data ingest: `deepchem.data.*` loaders
52
- - Feature extraction: `deepchem.feat.*`
53
- - Training APIs: `deepchem.models.*` / `deepchem.models.torch_models.*`
54
- - Evaluation: `deepchem.metrics.*`, `deepchem.utils.evaluate`
55
-
56
- ---
57
-
58
- ## 4) Available Tools and Endpoints
59
-
60
- Recommended MCP (Model Context Protocol) service endpoints:
61
-
62
- - `health_check`
63
- - Verify runtime, backend availability, and import status.
64
-
65
- - `list_capabilities`
66
- - Return installed backends/features (Torch/TF/JAX/RDKit, etc.).
67
-
68
- - `load_dataset`
69
- - Load datasets via MolNet (`load_tox21`, `load_delaney`, `load_qm9`, etc.) or file loaders.
70
-
71
- - `featurize_data`
72
- - Apply selected featurizer (ECFP, graph conv, SMILES tokenizer, protein/complex, material).
73
-
74
- - `split_dataset`
75
- - Apply splitters (random, scaffold, stratified, time/PDBBind, task-based).
76
-
77
- - `train_model`
78
- - Train model families (GraphConv, MPNN, GCN/GAT, multitask MLP, sklearn, GBDT).
79
-
80
- - `predict`
81
- - Run inference on prepared datasets.
82
-
83
- - `evaluate_model`
84
- - Compute metrics (ROC-AUC, PRC-AUC, RMSE, MAE, Pearson R², etc.).
85
-
86
- - `run_benchmark`
87
- - Execute MolNet benchmark workflow using selected model/split/featurizer.
88
-
89
- - `run_low_data_benchmark`
90
- - Execute low-data benchmark variants.
91
-
92
- - `export_results`
93
- - Save predictions, scores, and metadata to disk/artifact storage.
94
-
95
- ---
96
-
97
- ## 5) Common Issues and Notes
98
-
99
- - Dependency complexity is medium/high; install only required optional stacks.
100
- - RDKit is essential for many molecule features and loaders.
101
- - Torch/TF/JAX features are backend-specific; missing backend causes import/runtime failures.
102
- - Some benchmark datasets require downloading large files; expect longer setup times.
103
- - GPU acceleration depends on correct CUDA/backend build compatibility.
104
- - DGL/PyG-based models need matching versions with your PyTorch installation.
105
- - For reproducibility, set random seeds and pin package versions.
106
-
107
- ---
108
-
109
- ## 6) References
110
-
111
- - DeepChem repository: https://github.com/deepchem/deepchem
112
- - Main package docs (in repo): `README.md`, `docs/`
113
- - Examples: `examples/`
114
- - MolNet loaders: `deepchem/molnet/load_function/`
115
- - Benchmarks:
116
- - `python -m deepchem.molnet.run_benchmark`
117
- - `python -m deepchem.molnet.run_benchmark_low_data`
118
- - Contribution guide: `CONTRIBUTING.md`
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # DeepChem MCP Plugin
2
+
3
+ This directory contains the MCP service layer for DeepChem.
4
+
5
+ ## Exposed MCP Tools
6
+
7
+ ### 1) `health_check`
8
+ - **Description**: Report availability of runtime dependencies and adapter load status.
9
+ - **Parameters**: none
10
+ - **Example**:
11
+ ```json
12
+ {
13
+ "tool": "health_check",
14
+ "arguments": {}
15
+ }
16
+ ```
17
+
18
+ ### 2) `list_modules`
19
+ - **Description**: List DeepChem-related modules loaded by the adapter.
20
+ - **Parameters**:
21
+ - `state` (`str`, optional): `all`, `loaded`, or `failed`.
22
+ - **Example**:
23
+ ```json
24
+ {
25
+ "tool": "list_modules",
26
+ "arguments": {"state": "all"}
27
+ }
28
+ ```
29
+
30
+ ### 3) `list_symbols`
31
+ - **Description**: List public symbols in a loaded module.
32
+ - **Parameters**:
33
+ - `module_name` (`str`, required): for example `deepchem.feat`
34
+ - `pattern` (`str`, optional): regex filter
35
+ - `limit` (`int`, optional)
36
+ - **Example**:
37
+ ```json
38
+ {
39
+ "tool": "list_symbols",
40
+ "arguments": {
41
+ "module_name": "deepchem.feat",
42
+ "pattern": "Featurizer",
43
+ "limit": 50
44
+ }
45
+ }
46
+ ```
47
+
48
+ ### 4) `featurize_smiles`
49
+ - **Description**: Featurize SMILES strings with selected DeepChem featurizer.
50
+ - **Parameters**:
51
+ - `smiles` (`list[str]`, required)
52
+ - `featurizer` (`str`, optional): `circular`, `molgraph`, `convmol`
53
+ - `size` (`int`, optional): fingerprint size for `circular`
54
+ - `radius` (`int`, optional): fingerprint radius for `circular`
55
+ - **Example**:
56
+ ```json
57
+ {
58
+ "tool": "featurize_smiles",
59
+ "arguments": {
60
+ "smiles": ["CCO", "c1ccccc1"],
61
+ "featurizer": "circular",
62
+ "size": 1024,
63
+ "radius": 2
64
+ }
65
+ }
66
+ ```
67
+
68
+ ### 5) `split_indices`
69
+ - **Description**: Generate deterministic train/valid/test split indices.
70
+ - **Parameters**:
71
+ - `n_samples` (`int`, required)
72
+ - `frac_train` (`float`, optional)
73
+ - `frac_valid` (`float`, optional)
74
+ - `seed` (`int`, optional)
75
+ - **Example**:
76
+ ```json
77
+ {
78
+ "tool": "split_indices",
79
+ "arguments": {
80
+ "n_samples": 100,
81
+ "frac_train": 0.8,
82
+ "frac_valid": 0.1,
83
+ "seed": 123
84
+ }
85
+ }
86
+ ```
87
+
88
+ ### 6) `classification_metrics`
89
+ - **Description**: Compute binary classification metrics.
90
+ - **Parameters**:
91
+ - `y_true` (`list[int]`, required)
92
+ - `y_score` (`list[float]`, required)
93
+ - `threshold` (`float`, optional)
94
+ - **Example**:
95
+ ```json
96
+ {
97
+ "tool": "classification_metrics",
98
+ "arguments": {
99
+ "y_true": [1, 0, 1, 1],
100
+ "y_score": [0.9, 0.2, 0.4, 0.7],
101
+ "threshold": 0.5
102
+ }
103
+ }
104
+ ```
105
+
106
+ ### 7) `load_molnet_dataset`
107
+ - **Description**: Load a DeepChem MolNet dataset using the corresponding loader.
108
+ - **Parameters**:
109
+ - `dataset_name` (`str`, required): e.g. `delaney`, `tox21`
110
+ - `featurizer` (`str`, optional)
111
+ - `splitter` (`str`, optional)
112
+ - `reload` (`bool`, optional)
113
+ - **Example**:
114
+ ```json
115
+ {
116
+ "tool": "load_molnet_dataset",
117
+ "arguments": {
118
+ "dataset_name": "delaney",
119
+ "featurizer": "ECFP",
120
+ "splitter": "random",
121
+ "reload": false
122
+ }
123
+ }
124
+ ```
125
+
126
+ ## Run Locally (stdio)
127
+
128
+ ```bash
129
+ cd deepchem/mcp_output
130
+ python start_mcp.py
131
+ ```
132
+
133
+ Or:
134
+
135
+ ```bash
136
+ cd deepchem/mcp_output/mcp_plugin
137
+ python main.py
138
+ ```
139
+
140
+ ## Run as HTTP MCP Service
141
+
142
+ ```bash
143
+ cd deepchem/mcp_output
144
+ MCP_TRANSPORT=http MCP_PORT=8000 python start_mcp.py
145
+ ```
146
+
147
+ MCP HTTP endpoint will be available at:
148
+
149
+ - `http://localhost:8000/mcp`
deepchem/mcp_output/mcp_plugin/adapter.py CHANGED
@@ -1,481 +1,235 @@
1
- import os
2
- import sys
3
- import importlib
4
- from typing import Any, Dict, List, Optional, Tuple
5
-
6
- source_path = os.path.join(
7
- os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))),
8
- "source",
9
- )
10
- sys.path.insert(0, source_path)
11
-
12
 
13
- class Adapter:
14
- """
15
- Import-mode adapter for selected DeepChem repository modules.
16
-
17
- This adapter attempts to import and call concrete implementations discovered
18
- in repository analysis. It exposes:
19
- - Function wrappers for identified callable functions.
20
- - Class instance constructors for identified classes.
21
- - CLI fallback helpers when import path execution is not available.
22
 
23
- All methods return a unified dictionary with at least:
24
- {
25
- "status": "success" | "error" | "fallback",
26
- ...
27
- }
28
- """
29
 
30
- def __init__(self) -> None:
31
- self.mode = "import"
32
- self._modules: Dict[str, Any] = {}
33
- self._class_instances: Dict[str, Any] = {}
34
- self._import_errors: Dict[str, str] = {}
35
- self._load_modules()
36
 
37
- # -------------------------------------------------------------------------
38
- # Internal utilities
39
- # -------------------------------------------------------------------------
40
- def _result(self, status: str, **kwargs: Any) -> Dict[str, Any]:
41
- payload = {"status": status}
42
- payload.update(kwargs)
43
- return payload
44
 
45
- def _import_module(self, module_path: str) -> Optional[Any]:
46
- try:
47
- module = importlib.import_module(module_path)
48
- self._modules[module_path] = module
49
- return module
50
- except Exception as exc:
51
- self._import_errors[module_path] = (
52
- f"Failed to import '{module_path}'. "
53
- f"Verify dependencies and source path. Details: {exc}"
54
- )
55
- return None
56
 
57
- def _load_modules(self) -> None:
58
- module_paths = [
59
- "datasets.construct_pdbbind_df",
60
- "docs.source.conf",
61
- "contrib.visualization.utils",
62
- "contrib.DeepMHC.bd13_datasets",
63
- "contrib.DeepMHC.deepmhc",
64
- "contrib.dragonn.models",
 
 
 
 
 
 
 
 
 
 
65
  ]
66
- for mp in module_paths:
67
- self._import_module(mp)
68
-
69
- def health_check(self) -> Dict[str, Any]:
70
- """
71
- Report adapter readiness and import status.
72
-
73
- Returns:
74
- Dict with loaded modules and import errors.
75
- """
76
- return self._result(
77
- "success",
78
- mode=self.mode,
79
- loaded_modules=sorted(self._modules.keys()),
80
- import_errors=self._import_errors,
81
- guidance=(
82
- "Install optional scientific dependencies (e.g., rdkit, tensorflow, torch) "
83
- "if specific modules fail to import."
84
- ),
85
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
86
 
87
- def _call_function(
88
  self,
89
- module_path: str,
90
- function_name: str,
91
- *args: Any,
92
- **kwargs: Any,
93
  ) -> Dict[str, Any]:
94
- module = self._modules.get(module_path)
95
- if module is None:
96
- msg = self._import_errors.get(
97
- module_path,
98
- f"Module '{module_path}' is not available.",
99
- )
100
- return self._result(
101
- "fallback",
102
- error=msg,
103
- guidance=(
104
- f"Cannot call '{function_name}' because import failed. "
105
- "Check Python path and install missing dependencies."
106
- ),
107
- )
108
- fn = getattr(module, function_name, None)
109
- if fn is None:
110
- return self._result(
111
- "error",
112
- error=f"Function '{function_name}' not found in module '{module_path}'.",
113
- guidance="Verify repository version and function name.",
114
- )
115
  try:
116
- data = fn(*args, **kwargs)
117
- return self._result(
118
- "success",
119
- module=module_path,
120
- function=function_name,
121
- data=data,
122
- )
 
 
 
 
 
123
  except Exception as exc:
124
- return self._result(
125
- "error",
126
- error=f"Function call failed: {exc}",
127
- guidance="Validate function parameters and input data format.",
128
- )
129
 
130
- def _create_instance(
131
  self,
132
- module_path: str,
133
- class_name: str,
134
- instance_key: Optional[str] = None,
135
- *args: Any,
136
- **kwargs: Any,
137
  ) -> Dict[str, Any]:
138
- module = self._modules.get(module_path)
139
- if module is None:
140
- msg = self._import_errors.get(
141
- module_path,
142
- f"Module '{module_path}' is not available.",
143
- )
144
- return self._result(
145
- "fallback",
146
- error=msg,
147
- guidance=(
148
- f"Cannot instantiate '{class_name}' because import failed. "
149
- "Check dependency installation."
150
- ),
151
- )
152
- cls = getattr(module, class_name, None)
153
- if cls is None:
154
- return self._result(
155
- "error",
156
- error=f"Class '{class_name}' not found in module '{module_path}'.",
157
- guidance="Verify repository version and class name.",
158
- )
159
  try:
160
- instance = cls(*args, **kwargs)
161
- key = instance_key or f"{module_path}.{class_name}"
162
- self._class_instances[key] = instance
163
- return self._result(
164
- "success",
165
- module=module_path,
166
- class_name=class_name,
167
- instance_key=key,
168
- )
 
 
 
 
 
 
169
  except Exception as exc:
170
- return self._result(
171
- "error",
172
- error=f"Class instantiation failed: {exc}",
173
- guidance="Check constructor arguments and required runtime dependencies.",
174
- )
175
-
176
- # -------------------------------------------------------------------------
177
- # Functions: datasets.construct_pdbbind_df
178
- # -------------------------------------------------------------------------
179
- def call_construct_df(self, *args: Any, **kwargs: Any) -> Dict[str, Any]:
180
- """
181
- Call datasets.construct_pdbbind_df.construct_df.
182
-
183
- Parameters:
184
- *args: Positional arguments forwarded to construct_df.
185
- **kwargs: Keyword arguments forwarded to construct_df.
186
-
187
- Returns:
188
- Unified status dictionary with function output in 'data' on success.
189
- """
190
- return self._call_function("datasets.construct_pdbbind_df", "construct_df", *args, **kwargs)
191
-
192
- def call_extract_labels(self, *args: Any, **kwargs: Any) -> Dict[str, Any]:
193
- """
194
- Call datasets.construct_pdbbind_df.extract_labels.
195
-
196
- Parameters:
197
- *args: Positional arguments forwarded to extract_labels.
198
- **kwargs: Keyword arguments forwarded to extract_labels.
199
-
200
- Returns:
201
- Unified status dictionary with function output in 'data' on success.
202
- """
203
- return self._call_function("datasets.construct_pdbbind_df", "extract_labels", *args, **kwargs)
204
-
205
- # -------------------------------------------------------------------------
206
- # Functions: docs.source.conf
207
- # -------------------------------------------------------------------------
208
- def call_linkcode_resolve(self, *args: Any, **kwargs: Any) -> Dict[str, Any]:
209
- """
210
- Call docs.source.conf.linkcode_resolve.
211
-
212
- Parameters:
213
- *args: Positional arguments forwarded to linkcode_resolve.
214
- **kwargs: Keyword arguments forwarded to linkcode_resolve.
215
-
216
- Returns:
217
- Unified status dictionary with resolved link info in 'data' on success.
218
- """
219
- return self._call_function("docs.source.conf", "linkcode_resolve", *args, **kwargs)
220
-
221
- # -------------------------------------------------------------------------
222
- # Functions: contrib.visualization.utils
223
- # -------------------------------------------------------------------------
224
- def call_combine_mdtraj(self, *args: Any, **kwargs: Any) -> Dict[str, Any]:
225
- """
226
- Call contrib.visualization.utils.combine_mdtraj.
227
-
228
- Parameters:
229
- *args: Positional arguments forwarded to combine_mdtraj.
230
- **kwargs: Keyword arguments forwarded to combine_mdtraj.
231
-
232
- Returns:
233
- Unified status dictionary containing result in 'data' on success.
234
- """
235
- return self._call_function("contrib.visualization.utils", "combine_mdtraj", *args, **kwargs)
236
-
237
- def call_convert_lines_to_mdtraj(self, *args: Any, **kwargs: Any) -> Dict[str, Any]:
238
- """
239
- Call contrib.visualization.utils.convert_lines_to_mdtraj.
240
-
241
- Parameters:
242
- *args: Positional arguments forwarded to convert_lines_to_mdtraj.
243
- **kwargs: Keyword arguments forwarded to convert_lines_to_mdtraj.
244
-
245
- Returns:
246
- Unified status dictionary containing result in 'data' on success.
247
- """
248
- return self._call_function("contrib.visualization.utils", "convert_lines_to_mdtraj", *args, **kwargs)
249
-
250
- def call_display_images(self, *args: Any, **kwargs: Any) -> Dict[str, Any]:
251
- """
252
- Call contrib.visualization.utils.display_images.
253
-
254
- Parameters:
255
- *args: Positional arguments forwarded to display_images.
256
- **kwargs: Keyword arguments forwarded to display_images.
257
-
258
- Returns:
259
- Unified status dictionary containing result in 'data' on success.
260
- """
261
- return self._call_function("contrib.visualization.utils", "display_images", *args, **kwargs)
262
-
263
- # -------------------------------------------------------------------------
264
- # Functions: contrib.DeepMHC.bd13_datasets
265
- # -------------------------------------------------------------------------
266
- def call_featurize(self, *args: Any, **kwargs: Any) -> Dict[str, Any]:
267
- """
268
- Call contrib.DeepMHC.bd13_datasets.featurize.
269
-
270
- Parameters:
271
- *args: Positional arguments forwarded to featurize.
272
- **kwargs: Keyword arguments forwarded to featurize.
273
-
274
- Returns:
275
- Unified status dictionary containing featurized output in 'data' on success.
276
- """
277
- return self._call_function("contrib.DeepMHC.bd13_datasets", "featurize", *args, **kwargs)
278
-
279
- def call_load_bd2013_human(self, *args: Any, **kwargs: Any) -> Dict[str, Any]:
280
- """
281
- Call contrib.DeepMHC.bd13_datasets.load_bd2013_human.
282
-
283
- Parameters:
284
- *args: Positional arguments forwarded to load_bd2013_human.
285
- **kwargs: Keyword arguments forwarded to load_bd2013_human.
286
-
287
- Returns:
288
- Unified status dictionary with loaded dataset artifacts in 'data' on success.
289
- """
290
- return self._call_function("contrib.DeepMHC.bd13_datasets", "load_bd2013_human", *args, **kwargs)
291
-
292
- def call_to_one_hot_array(self, *args: Any, **kwargs: Any) -> Dict[str, Any]:
293
- """
294
- Call contrib.DeepMHC.bd13_datasets.to_one_hot_array.
295
-
296
- Parameters:
297
- *args: Positional arguments forwarded to to_one_hot_array.
298
- **kwargs: Keyword arguments forwarded to to_one_hot_array.
299
-
300
- Returns:
301
- Unified status dictionary with encoded array in 'data' on success.
302
- """
303
- return self._call_function("contrib.DeepMHC.bd13_datasets", "to_one_hot_array", *args, **kwargs)
304
-
305
- # -------------------------------------------------------------------------
306
- # Classes: contrib.DeepMHC.deepmhc.DeepMHC
307
- # -------------------------------------------------------------------------
308
- def create_deepmhc(self, *args: Any, instance_key: Optional[str] = None, **kwargs: Any) -> Dict[str, Any]:
309
- """
310
- Instantiate contrib.DeepMHC.deepmhc.DeepMHC.
311
-
312
- Parameters:
313
- *args: Positional constructor args.
314
- instance_key: Optional storage key for later retrieval.
315
- **kwargs: Keyword constructor args.
316
-
317
- Returns:
318
- Unified status dictionary with 'instance_key' on success.
319
- """
320
- return self._create_instance(
321
- "contrib.DeepMHC.deepmhc",
322
- "DeepMHC",
323
- instance_key,
324
- *args,
325
- **kwargs,
326
- )
327
-
328
- # -------------------------------------------------------------------------
329
- # Classes: contrib.dragonn.models
330
- # -------------------------------------------------------------------------
331
- def create_motif_score_rnn(self, *args: Any, instance_key: Optional[str] = None, **kwargs: Any) -> Dict[str, Any]:
332
- """
333
- Instantiate contrib.dragonn.models.MotifScoreRNN.
334
-
335
- Parameters:
336
- *args: Positional constructor args.
337
- instance_key: Optional storage key for later retrieval.
338
- **kwargs: Keyword constructor args.
339
-
340
- Returns:
341
- Unified status dictionary with 'instance_key' on success.
342
- """
343
- return self._create_instance(
344
- "contrib.dragonn.models",
345
- "MotifScoreRNN",
346
- instance_key,
347
- *args,
348
- **kwargs,
349
- )
350
-
351
- def create_gkmsvm(self, *args: Any, instance_key: Optional[str] = None, **kwargs: Any) -> Dict[str, Any]:
352
- """
353
- Instantiate contrib.dragonn.models.gkmSVM.
354
-
355
- Parameters:
356
- *args: Positional constructor args.
357
- instance_key: Optional storage key for later retrieval.
358
- **kwargs: Keyword constructor args.
359
-
360
- Returns:
361
- Unified status dictionary with 'instance_key' on success.
362
- """
363
- return self._create_instance(
364
- "contrib.dragonn.models",
365
- "gkmSVM",
366
- instance_key,
367
- *args,
368
- **kwargs,
369
- )
370
-
371
- # -------------------------------------------------------------------------
372
- # Instance management
373
- # -------------------------------------------------------------------------
374
- def list_instances(self) -> Dict[str, Any]:
375
- """
376
- List cached class instances created by this adapter.
377
-
378
- Returns:
379
- Unified status dictionary containing instance keys.
380
- """
381
- return self._result("success", instances=sorted(self._class_instances.keys()))
382
-
383
- def call_instance_method(
384
  self,
385
- instance_key: str,
386
- method_name: str,
387
- *args: Any,
388
- **kwargs: Any,
389
  ) -> Dict[str, Any]:
390
- """
391
- Invoke a method on a previously created instance.
392
-
393
- Parameters:
394
- instance_key: Key returned by create_* method.
395
- method_name: Target method name on the instance.
396
- *args: Positional arguments for the instance method.
397
- **kwargs: Keyword arguments for the instance method.
 
 
 
 
 
 
 
398
 
399
- Returns:
400
- Unified status dictionary with method result in 'data' on success.
401
- """
402
- inst = self._class_instances.get(instance_key)
403
- if inst is None:
404
- return self._result(
405
- "error",
406
- error=f"Instance '{instance_key}' not found.",
407
- guidance="Create the instance first using the matching create_* method.",
408
- )
409
- method = getattr(inst, method_name, None)
410
- if method is None or not callable(method):
411
- return self._result(
412
- "error",
413
- error=f"Method '{method_name}' not found on instance '{instance_key}'.",
414
- guidance="Check available methods with dir(instance).",
415
- )
416
  try:
417
- data = method(*args, **kwargs)
418
- return self._result("success", instance_key=instance_key, method=method_name, data=data)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
419
  except Exception as exc:
420
- return self._result(
421
- "error",
422
- error=f"Instance method call failed: {exc}",
423
- guidance="Validate method parameters and ensure model state is initialized.",
424
- )
425
-
426
- # -------------------------------------------------------------------------
427
- # CLI fallback helpers
428
- # -------------------------------------------------------------------------
429
- def cli_run_benchmark(self, extra_args: Optional[List[str]] = None) -> Dict[str, Any]:
430
- """
431
- Provide CLI fallback guidance for MolNet benchmark execution.
432
-
433
- Parameters:
434
- extra_args: Optional CLI args to append.
435
-
436
- Returns:
437
- Unified status dictionary with a suggested command.
438
- """
439
- cmd = ["python", "-m", "source.deepchem.molnet.run_benchmark"] + (extra_args or [])
440
- return self._result(
441
- "fallback",
442
- command=cmd,
443
- guidance=(
444
- "Use this command in a shell when import-mode execution is unavailable. "
445
- "Ensure dependencies are installed and run from project root."
446
- ),
447
- )
448
-
449
- def cli_run_benchmark_low_data(self, extra_args: Optional[List[str]] = None) -> Dict[str, Any]:
450
- """
451
- Provide CLI fallback guidance for low-data benchmark workflow.
452
-
453
- Parameters:
454
- extra_args: Optional CLI args to append.
455
-
456
- Returns:
457
- Unified status dictionary with a suggested command.
458
- """
459
- cmd = ["python", "-m", "source.deepchem.molnet.run_benchmark_low_data"] + (extra_args or [])
460
- return self._result(
461
- "fallback",
462
- command=cmd,
463
- guidance="Run in shell as fallback. Verify low-data benchmark options before execution.",
464
- )
465
-
466
- def cli_run_example_benchmark(self, extra_args: Optional[List[str]] = None) -> Dict[str, Any]:
467
- """
468
- Provide CLI fallback guidance for example benchmark runner.
469
-
470
- Parameters:
471
- extra_args: Optional CLI args to append.
472
-
473
- Returns:
474
- Unified status dictionary with a suggested command.
475
- """
476
- cmd = ["python", "-m", "source.examples.benchmark"] + (extra_args or [])
477
- return self._result(
478
- "fallback",
479
- command=cmd,
480
- guidance="This is a demo-oriented runner and may be less stable than core benchmark scripts.",
481
- )
 
1
+ from __future__ import annotations
 
 
 
 
 
 
 
 
 
 
2
 
3
+ import importlib
4
+ import inspect
5
+ import re
6
+ import sys
7
+ from pathlib import Path
8
+ from typing import Any, Dict, List, Optional
 
 
 
9
 
10
+ SOURCE_DIR = (Path(__file__).resolve().parent / ".." / ".." / "source").resolve()
11
+ if str(SOURCE_DIR) not in sys.path:
12
+ sys.path.insert(0, str(SOURCE_DIR))
 
 
 
13
 
14
+ CURRENT_PATH = Path(__file__).resolve()
15
+ for parent in CURRENT_PATH.parents:
16
+ candidate = parent / "source"
17
+ if candidate.is_dir() and str(candidate) not in sys.path:
18
+ sys.path.insert(0, str(candidate))
19
+ break
20
 
21
+ try:
22
+ import deepchem as _deepchem_probe # type: ignore
23
+ except Exception:
24
+ _deepchem_probe = None
 
 
 
25
 
 
 
 
 
 
 
 
 
 
 
 
26
 
27
+ class Adapter:
28
+ """Dynamic adapter for loading and invoking target library symbols."""
29
+
30
+ def __init__(self, module_candidates: Optional[List[str]] = None) -> None:
31
+ self.module_candidates = module_candidates or [
32
+ "deepchem",
33
+ "deepchem.data",
34
+ "deepchem.feat",
35
+ "deepchem.metrics",
36
+ "deepchem.models",
37
+ "deepchem.molnet",
38
+ "deepchem.splits",
39
+ "deepchem.trans",
40
+ "deepchem.utils",
41
+ "deepchem.dock",
42
+ "deepchem.hyper",
43
+ "deepchem.rl",
44
+ "deepchem.metalearning",
45
  ]
46
+ self.loaded_modules: Dict[str, Any] = {}
47
+ self.failed_modules: Dict[str, str] = {}
48
+ self.mode = "normal"
49
+ self.load_modules()
50
+
51
+ def load_modules(self) -> Dict[str, Any]:
52
+ """Load configured modules via importlib with graceful fallbacks."""
53
+ self.loaded_modules = {}
54
+ self.failed_modules = {}
55
+
56
+ for module_name in self.module_candidates:
57
+ try:
58
+ module = importlib.import_module(module_name)
59
+ self.loaded_modules[module_name] = module
60
+ except Exception as exc:
61
+ self.failed_modules[module_name] = str(exc)
62
+
63
+ self.mode = "normal" if self.loaded_modules else "blackbox"
64
+ status = "ok" if self.loaded_modules else "fallback"
65
+ return {
66
+ "status": status,
67
+ "mode": self.mode,
68
+ "loaded_count": len(self.loaded_modules),
69
+ "failed_count": len(self.failed_modules),
70
+ }
71
+
72
+ def health(self) -> Dict[str, Any]:
73
+ """Return module loading health and operating mode."""
74
+ status = "ok" if self.loaded_modules else "fallback"
75
+ return {
76
+ "status": status,
77
+ "mode": self.mode,
78
+ "loaded_count": len(self.loaded_modules),
79
+ "failed_count": len(self.failed_modules),
80
+ "loaded_modules": sorted(self.loaded_modules.keys()),
81
+ "failed_modules": self.failed_modules,
82
+ }
83
+
84
+ def list_modules(self, state: str = "all") -> Dict[str, Any]:
85
+ """List loaded and/or failed modules.
86
+
87
+ Parameters
88
+ ----------
89
+ state: str
90
+ One of ``all``, ``loaded``, or ``failed``.
91
+ """
92
+ state = (state or "all").lower()
93
+ if state not in {"all", "loaded", "failed"}:
94
+ return {
95
+ "status": "error",
96
+ "error": "state must be one of: all, loaded, failed",
97
+ }
98
+
99
+ payload: Dict[str, Any] = {
100
+ "status": "ok",
101
+ "mode": self.mode,
102
+ "loaded_count": len(self.loaded_modules),
103
+ "failed_count": len(self.failed_modules),
104
+ }
105
+ if state in {"all", "loaded"}:
106
+ payload["loaded_modules"] = sorted(self.loaded_modules.keys())
107
+ if state in {"all", "failed"}:
108
+ payload["failed_modules"] = self.failed_modules
109
+ return payload
110
 
111
+ def list_symbols(
112
  self,
113
+ module_name: str,
114
+ pattern: str = "",
115
+ limit: int = 200,
 
116
  ) -> Dict[str, Any]:
117
+ """List public symbols in a loaded module, optionally filtered by regex."""
118
+ if module_name not in self.loaded_modules:
119
+ return {
120
+ "status": "error",
121
+ "error": f"module not loaded: {module_name}",
122
+ }
123
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
124
  try:
125
+ module = self.loaded_modules[module_name]
126
+ names = [name for name in dir(module) if not name.startswith("_")]
127
+ if pattern:
128
+ matcher = re.compile(pattern)
129
+ names = [name for name in names if matcher.search(name)]
130
+ names = sorted(names)[:max(1, limit)]
131
+ return {
132
+ "status": "ok",
133
+ "module": module_name,
134
+ "count": len(names),
135
+ "symbols": names,
136
+ }
137
  except Exception as exc:
138
+ return {
139
+ "status": "error",
140
+ "module": module_name,
141
+ "error": str(exc),
142
+ }
143
 
144
+ def call_function(
145
  self,
146
+ module_name: str,
147
+ function_name: str,
148
+ kwargs: Optional[Dict[str, Any]] = None,
 
 
149
  ) -> Dict[str, Any]:
150
+ """Call a function from a loaded module using keyword arguments only."""
151
+ kwargs = kwargs or {}
152
+
153
+ if self.mode == "blackbox":
154
+ return {
155
+ "status": "fallback",
156
+ "mode": self.mode,
157
+ "error": "adapter is in blackbox mode; no modules loaded",
158
+ }
159
+
160
+ if module_name not in self.loaded_modules:
161
+ return {
162
+ "status": "error",
163
+ "error": f"module not loaded: {module_name}",
164
+ }
165
+
 
 
 
 
 
166
  try:
167
+ module = self.loaded_modules[module_name]
168
+ fn = getattr(module, function_name, None)
169
+ if fn is None or not callable(fn):
170
+ return {
171
+ "status": "error",
172
+ "error": f"function not found or not callable: {function_name}",
173
+ }
174
+
175
+ result = fn(**kwargs)
176
+ return {
177
+ "status": "ok",
178
+ "module": module_name,
179
+ "function": function_name,
180
+ "result": result,
181
+ }
182
  except Exception as exc:
183
+ return {
184
+ "status": "error",
185
+ "module": module_name,
186
+ "function": function_name,
187
+ "error": str(exc),
188
+ }
189
+
190
+ def create_instance(
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
191
  self,
192
+ module_name: str,
193
+ class_name: str,
194
+ kwargs: Optional[Dict[str, Any]] = None,
 
195
  ) -> Dict[str, Any]:
196
+ """Create a class instance from a loaded module using keyword arguments."""
197
+ kwargs = kwargs or {}
198
+
199
+ if self.mode == "blackbox":
200
+ return {
201
+ "status": "fallback",
202
+ "mode": self.mode,
203
+ "error": "adapter is in blackbox mode; no modules loaded",
204
+ }
205
+
206
+ if module_name not in self.loaded_modules:
207
+ return {
208
+ "status": "error",
209
+ "error": f"module not loaded: {module_name}",
210
+ }
211
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
212
  try:
213
+ module = self.loaded_modules[module_name]
214
+ cls = getattr(module, class_name, None)
215
+ if cls is None or not inspect.isclass(cls):
216
+ return {
217
+ "status": "error",
218
+ "error": f"class not found: {class_name}",
219
+ }
220
+
221
+ instance = cls(**kwargs)
222
+ return {
223
+ "status": "ok",
224
+ "module": module_name,
225
+ "class": class_name,
226
+ "instance_type": type(instance).__name__,
227
+ "instance_repr": repr(instance),
228
+ }
229
  except Exception as exc:
230
+ return {
231
+ "status": "error",
232
+ "module": module_name,
233
+ "class": class_name,
234
+ "error": str(exc),
235
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
deepchem/mcp_output/mcp_plugin/main.py CHANGED
@@ -1,13 +1,6 @@
1
- """
2
- MCP Service Auto-Wrapper - Auto-generated
3
- """
4
- from mcp_service import create_app
5
-
6
- def main():
7
- """Main entry point"""
8
- app = create_app()
9
- return app
10
 
 
11
  if __name__ == "__main__":
12
- app = main()
13
- app.run()
 
1
+ from .mcp_service import create_app
 
 
 
 
 
 
 
 
2
 
3
+ # Local stdio entry point for Claude Desktop / CLI only.
4
  if __name__ == "__main__":
5
+ app = create_app()
6
+ app.run()
deepchem/mcp_output/mcp_plugin/mcp_service.py CHANGED
@@ -1,240 +1,373 @@
 
 
1
  import os
2
  import sys
 
 
3
 
4
- source_path = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))), "source")
5
- if source_path not in sys.path:
6
- sys.path.insert(0, source_path)
7
 
8
- from fastmcp import FastMCP
 
 
 
 
 
9
 
10
- from datasets.construct_pdbbind_df import construct_df, extract_labels
11
- from docs.source.conf import linkcode_resolve
12
- from contrib.visualization.utils import combine_mdtraj, display_images, convert_lines_to_mdtraj
13
- from contrib.DeepMHC.bd13_datasets import load_bd2013_human, to_one_hot_array, featurize
14
- from contrib.DeepMHC.deepmhc import DeepMHC
15
- from contrib.dragonn.models import MotifScoreRNN, gkmSVM
16
 
17
- mcp = FastMCP("unknown_service")
 
 
 
18
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
 
20
- @mcp.tool(name="construct_df", description="Auto-wrapped function construct_df")
21
- def construct_df(payload: dict):
22
- try:
23
- if construct_df is None:
24
- return {"success": False, "result": None, "error": "Function construct_df is not available"}
25
- result = construct_df(**payload)
26
- return {"success": True, "result": result, "error": None}
27
- except Exception as e:
28
- return {"success": False, "result": None, "error": str(e)}
29
-
30
- @mcp.tool(name="extract_labels", description="Auto-wrapped function extract_labels")
31
- def extract_labels(payload: dict):
32
- try:
33
- if extract_labels is None:
34
- return {"success": False, "result": None, "error": "Function extract_labels is not available"}
35
- result = extract_labels(**payload)
36
- return {"success": True, "result": result, "error": None}
37
- except Exception as e:
38
- return {"success": False, "result": None, "error": str(e)}
39
-
40
- @mcp.tool(name="linkcode_resolve", description="Auto-wrapped function linkcode_resolve")
41
- def linkcode_resolve(payload: dict):
42
- try:
43
- if linkcode_resolve is None:
44
- return {"success": False, "result": None, "error": "Function linkcode_resolve is not available"}
45
- result = linkcode_resolve(**payload)
46
- return {"success": True, "result": result, "error": None}
47
- except Exception as e:
48
- return {"success": False, "result": None, "error": str(e)}
49
-
50
- @mcp.tool(name="combine_mdtraj", description="Auto-wrapped function combine_mdtraj")
51
- def combine_mdtraj(payload: dict):
52
- try:
53
- if combine_mdtraj is None:
54
- return {"success": False, "result": None, "error": "Function combine_mdtraj is not available"}
55
- result = combine_mdtraj(**payload)
56
- return {"success": True, "result": result, "error": None}
57
- except Exception as e:
58
- return {"success": False, "result": None, "error": str(e)}
59
-
60
- @mcp.tool(name="convert_lines_to_mdtraj", description="Auto-wrapped function convert_lines_to_mdtraj")
61
- def convert_lines_to_mdtraj(payload: dict):
62
- try:
63
- if convert_lines_to_mdtraj is None:
64
- return {"success": False, "result": None, "error": "Function convert_lines_to_mdtraj is not available"}
65
- result = convert_lines_to_mdtraj(**payload)
66
- return {"success": True, "result": result, "error": None}
67
- except Exception as e:
68
- return {"success": False, "result": None, "error": str(e)}
69
-
70
- @mcp.tool(name="display_images", description="Auto-wrapped function display_images")
71
- def display_images(payload: dict):
72
- try:
73
- if display_images is None:
74
- return {"success": False, "result": None, "error": "Function display_images is not available"}
75
- result = display_images(**payload)
76
- return {"success": True, "result": result, "error": None}
77
- except Exception as e:
78
- return {"success": False, "result": None, "error": str(e)}
79
-
80
- @mcp.tool(name="featurize", description="Auto-wrapped function featurize")
81
- def featurize(payload: dict):
82
- try:
83
- if featurize is None:
84
- return {"success": False, "result": None, "error": "Function featurize is not available"}
85
- result = featurize(**payload)
86
- return {"success": True, "result": result, "error": None}
87
- except Exception as e:
88
- return {"success": False, "result": None, "error": str(e)}
89
-
90
- @mcp.tool(name="load_bd2013_human", description="Auto-wrapped function load_bd2013_human")
91
- def load_bd2013_human(payload: dict):
92
- try:
93
- if load_bd2013_human is None:
94
- return {"success": False, "result": None, "error": "Function load_bd2013_human is not available"}
95
- result = load_bd2013_human(**payload)
96
- return {"success": True, "result": result, "error": None}
97
- except Exception as e:
98
- return {"success": False, "result": None, "error": str(e)}
99
-
100
- @mcp.tool(name="to_one_hot_array", description="Auto-wrapped function to_one_hot_array")
101
- def to_one_hot_array(payload: dict):
102
  try:
103
- if to_one_hot_array is None:
104
- return {"success": False, "result": None, "error": "Function to_one_hot_array is not available"}
105
- result = to_one_hot_array(**payload)
106
- return {"success": True, "result": result, "error": None}
107
- except Exception as e:
108
- return {"success": False, "result": None, "error": str(e)}
109
-
110
- @mcp.tool(name="deepmhc", description="DeepMHC class")
111
- def deepmhc(*args, **kwargs):
112
- """DeepMHC class"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
113
  try:
114
- if DeepMHC is None:
115
- return {"success": False, "result": None, "error": "Class DeepMHC is not available, path may need adjustment"}
116
-
117
- # MCP parameter type conversion
118
- converted_args = []
119
- converted_kwargs = kwargs.copy()
120
-
121
- # Handle position argument type conversion
122
- for arg in args:
123
- if isinstance(arg, str):
124
- # Try to convert to numeric type
125
- try:
126
- if '.' in arg:
127
- converted_args.append(float(arg))
128
- else:
129
- converted_args.append(int(arg))
130
- except ValueError:
131
- converted_args.append(arg)
132
- else:
133
- converted_args.append(arg)
134
-
135
- # Handle keyword argument type conversion
136
- for key, value in converted_kwargs.items():
137
- if isinstance(value, str):
138
- try:
139
- if '.' in value:
140
- converted_kwargs[key] = float(value)
141
- else:
142
- converted_kwargs[key] = int(value)
143
- except ValueError:
144
- pass
145
-
146
- instance = DeepMHC(*converted_args, **converted_kwargs)
147
- return {"success": True, "result": str(instance), "error": None}
148
- except Exception as e:
149
- return {"success": False, "result": None, "error": str(e)}
150
-
151
- @mcp.tool(name="motifscorernn", description="MotifScoreRNN class")
152
- def motifscorernn(*args, **kwargs):
153
- """MotifScoreRNN class"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
154
  try:
155
- if MotifScoreRNN is None:
156
- return {"success": False, "result": None, "error": "Class MotifScoreRNN is not available, path may need adjustment"}
157
-
158
- # MCP parameter type conversion
159
- converted_args = []
160
- converted_kwargs = kwargs.copy()
161
-
162
- # Handle position argument type conversion
163
- for arg in args:
164
- if isinstance(arg, str):
165
- # Try to convert to numeric type
166
- try:
167
- if '.' in arg:
168
- converted_args.append(float(arg))
169
- else:
170
- converted_args.append(int(arg))
171
- except ValueError:
172
- converted_args.append(arg)
173
- else:
174
- converted_args.append(arg)
175
-
176
- # Handle keyword argument type conversion
177
- for key, value in converted_kwargs.items():
178
- if isinstance(value, str):
179
- try:
180
- if '.' in value:
181
- converted_kwargs[key] = float(value)
182
- else:
183
- converted_kwargs[key] = int(value)
184
- except ValueError:
185
- pass
186
-
187
- instance = MotifScoreRNN(*converted_args, **converted_kwargs)
188
- return {"success": True, "result": str(instance), "error": None}
189
- except Exception as e:
190
- return {"success": False, "result": None, "error": str(e)}
191
-
192
- @mcp.tool(name="gkmsvm", description="gkmSVM class")
193
- def gkmsvm(*args, **kwargs):
194
- """gkmSVM class"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
195
  try:
196
- if gkmSVM is None:
197
- return {"success": False, "result": None, "error": "Class gkmSVM is not available, path may need adjustment"}
198
-
199
- # MCP parameter type conversion
200
- converted_args = []
201
- converted_kwargs = kwargs.copy()
202
-
203
- # Handle position argument type conversion
204
- for arg in args:
205
- if isinstance(arg, str):
206
- # Try to convert to numeric type
207
- try:
208
- if '.' in arg:
209
- converted_args.append(float(arg))
210
- else:
211
- converted_args.append(int(arg))
212
- except ValueError:
213
- converted_args.append(arg)
214
- else:
215
- converted_args.append(arg)
216
-
217
- # Handle keyword argument type conversion
218
- for key, value in converted_kwargs.items():
219
- if isinstance(value, str):
220
- try:
221
- if '.' in value:
222
- converted_kwargs[key] = float(value)
223
- else:
224
- converted_kwargs[key] = int(value)
225
- except ValueError:
226
- pass
227
-
228
- instance = gkmSVM(*converted_args, **converted_kwargs)
229
- return {"success": True, "result": str(instance), "error": None}
230
- except Exception as e:
231
- return {"success": False, "result": None, "error": str(e)}
232
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
233
 
234
 
235
  def create_app():
236
- """Create and return FastMCP application instance"""
237
  return mcp
238
 
 
239
  if __name__ == "__main__":
240
- mcp.run(transport="http", host="0.0.0.0", port=8000)
 
1
+ from __future__ import annotations
2
+
3
  import os
4
  import sys
5
+ from pathlib import Path
6
+ from typing import Any, Dict, List
7
 
8
+ SOURCE_DIR = (Path(__file__).resolve().parent / ".." / ".." / "source").resolve()
9
+ if str(SOURCE_DIR) not in sys.path:
10
+ sys.path.insert(0, str(SOURCE_DIR))
11
 
12
+ CURRENT_PATH = Path(__file__).resolve()
13
+ for parent in CURRENT_PATH.parents:
14
+ candidate = parent / "source"
15
+ if candidate.is_dir() and str(candidate) not in sys.path:
16
+ sys.path.insert(0, str(candidate))
17
+ break
18
 
19
+ try:
20
+ from fastmcp import FastMCP # type: ignore
21
+ except Exception:
22
+ FastMCP = None
 
 
23
 
24
+ try:
25
+ import deepchem as dc # type: ignore
26
+ except Exception:
27
+ dc = None
28
 
29
+ try:
30
+ import numpy as np # type: ignore
31
+ except Exception:
32
+ np = None
33
+
34
+ try:
35
+ import pandas as pd # type: ignore
36
+ except Exception:
37
+ pd = None
38
+
39
+ try:
40
+ import sklearn # type: ignore
41
+ except Exception:
42
+ sklearn = None
43
+
44
+ try:
45
+ import rdkit # type: ignore
46
+ except Exception:
47
+ rdkit = None
48
+
49
+ from .adapter import Adapter
50
+
51
+
52
+ class _FallbackMCP:
53
+ def __init__(self, name: str) -> None:
54
+ self.name = name
55
+ self.tools: List[Any] = []
56
+
57
+ def tool(self, name: str, description: str):
58
+ def decorator(fn):
59
+ fn.name = name
60
+ fn.description = description
61
+ self.tools.append(fn)
62
+ return fn
63
+
64
+ return decorator
65
+
66
+ def run(self, **_: Any) -> None:
67
+ raise RuntimeError("fastmcp is not installed. Please install fastmcp to run this service.")
68
+
69
+
70
+ mcp = FastMCP("deepchem-mcp-service") if FastMCP is not None else _FallbackMCP("deepchem-mcp-service")
71
+ adapter = Adapter()
72
+
73
+
74
+ def _response(success: bool, result: Any = None, error: str | None = None) -> Dict[str, Any]:
75
+ return {"success": success, "result": result, "error": error}
76
+
77
+
78
+ @mcp.tool(name="health_check", description="Report availability of dependencies and adapter health.")
79
+ def health_check() -> Dict[str, Any]:
80
+ """Get health status for dependencies and dynamic module loader.
81
+
82
+ Returns
83
+ -------
84
+ dict
85
+ Standard MCP response with dependency availability and adapter diagnostics.
86
+ """
87
+ result = {
88
+ "service": "deepchem-mcp-service",
89
+ "transport_default": os.getenv("MCP_TRANSPORT", "stdio"),
90
+ "dependencies": {
91
+ "fastmcp": FastMCP is not None,
92
+ "deepchem": dc is not None,
93
+ "numpy": np is not None,
94
+ "pandas": pd is not None,
95
+ "scikit-learn": sklearn is not None,
96
+ "rdkit": rdkit is not None,
97
+ },
98
+ "adapter": adapter.health(),
99
+ }
100
+ return _response(True, result=result, error=None)
101
+
102
+
103
+ @mcp.tool(name="list_modules", description="List deepchem-related modules loaded by the adapter.")
104
+ def list_modules(state: str = "all") -> Dict[str, Any]:
105
+ """List loaded and failed modules.
106
+
107
+ Parameters
108
+ ----------
109
+ state: str
110
+ Filter output by module state: ``all``, ``loaded``, or ``failed``.
111
+ """
112
+ payload = adapter.list_modules(state=state)
113
+ if payload.get("status") == "error":
114
+ return _response(False, result=None, error=payload.get("error", "unknown error"))
115
+ return _response(True, result=payload, error=None)
116
+
117
+
118
+ @mcp.tool(name="list_symbols", description="List public symbols from a loaded deepchem module.")
119
+ def list_symbols(module_name: str, pattern: str = "", limit: int = 120) -> Dict[str, Any]:
120
+ """List symbol names in a module.
121
+
122
+ Parameters
123
+ ----------
124
+ module_name: str
125
+ Fully qualified module name, for example ``deepchem.feat``.
126
+ pattern: str
127
+ Optional regular expression to filter symbol names.
128
+ limit: int
129
+ Maximum number of symbols to return.
130
+ """
131
+ payload = adapter.list_symbols(module_name=module_name, pattern=pattern, limit=limit)
132
+ if payload.get("status") == "error":
133
+ return _response(False, result=None, error=payload.get("error", "unknown error"))
134
+ return _response(True, result=payload, error=None)
135
+
136
+
137
+ @mcp.tool(name="featurize_smiles", description="Generate DeepChem features for SMILES strings.")
138
+ def featurize_smiles(
139
+ smiles: List[str],
140
+ featurizer: str = "circular",
141
+ size: int = 1024,
142
+ radius: int = 2,
143
+ ) -> Dict[str, Any]:
144
+ """Featurize a list of SMILES strings.
145
+
146
+ Parameters
147
+ ----------
148
+ smiles: list[str]
149
+ Input SMILES strings.
150
+ featurizer: str
151
+ One of ``circular``, ``molgraph``, or ``convmol``.
152
+ size: int
153
+ Fingerprint size when using circular fingerprints.
154
+ radius: int
155
+ Fingerprint radius when using circular fingerprints.
156
+ """
157
+ if dc is None:
158
+ return _response(False, result=None, error="deepchem is not available")
159
+
160
+ if not smiles:
161
+ return _response(False, result=None, error="smiles cannot be empty")
162
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
163
  try:
164
+ choice = featurizer.lower().strip()
165
+ if choice == "circular":
166
+ feat = dc.feat.CircularFingerprint(size=size, radius=radius)
167
+ elif choice == "molgraph":
168
+ feat = dc.feat.MolGraphConvFeaturizer()
169
+ elif choice == "convmol":
170
+ feat = dc.feat.ConvMolFeaturizer()
171
+ else:
172
+ return _response(False, result=None, error="featurizer must be one of: circular, molgraph, convmol")
173
+
174
+ values = feat.featurize(smiles)
175
+ feature_types = [type(item).__name__ for item in values]
176
+ return _response(
177
+ True,
178
+ result={
179
+ "n_inputs": len(smiles),
180
+ "n_outputs": len(values),
181
+ "featurizer": choice,
182
+ "feature_types": feature_types,
183
+ },
184
+ error=None,
185
+ )
186
+ except Exception as exc:
187
+ return _response(False, result=None, error=str(exc))
188
+
189
+
190
+ @mcp.tool(name="split_indices", description="Create randomized train/valid/test index splits.")
191
+ def split_indices(
192
+ n_samples: int,
193
+ frac_train: float = 0.8,
194
+ frac_valid: float = 0.1,
195
+ seed: int = 123,
196
+ ) -> Dict[str, Any]:
197
+ """Create deterministic shuffled indices for dataset splitting.
198
+
199
+ Parameters
200
+ ----------
201
+ n_samples: int
202
+ Number of samples in the dataset.
203
+ frac_train: float
204
+ Fraction of samples assigned to train.
205
+ frac_valid: float
206
+ Fraction assigned to validation.
207
+ seed: int
208
+ Random seed for reproducible splits.
209
+ """
210
+ if np is None:
211
+ return _response(False, result=None, error="numpy is not available")
212
+
213
+ if n_samples <= 0:
214
+ return _response(False, result=None, error="n_samples must be positive")
215
+
216
+ if frac_train <= 0 or frac_valid < 0 or frac_train + frac_valid >= 1:
217
+ return _response(False, result=None, error="fractions must satisfy: frac_train > 0, frac_valid >= 0, frac_train + frac_valid < 1")
218
+
219
  try:
220
+ rng = np.random.default_rng(seed)
221
+ indices = np.arange(n_samples)
222
+ rng.shuffle(indices)
223
+
224
+ train_end = int(n_samples * frac_train)
225
+ valid_end = train_end + int(n_samples * frac_valid)
226
+
227
+ train_idx = indices[:train_end].tolist()
228
+ valid_idx = indices[train_end:valid_end].tolist()
229
+ test_idx = indices[valid_end:].tolist()
230
+
231
+ return _response(
232
+ True,
233
+ result={
234
+ "n_samples": n_samples,
235
+ "counts": {
236
+ "train": len(train_idx),
237
+ "valid": len(valid_idx),
238
+ "test": len(test_idx),
239
+ },
240
+ "indices": {
241
+ "train": train_idx,
242
+ "valid": valid_idx,
243
+ "test": test_idx,
244
+ },
245
+ },
246
+ error=None,
247
+ )
248
+ except Exception as exc:
249
+ return _response(False, result=None, error=str(exc))
250
+
251
+
252
+ @mcp.tool(name="classification_metrics", description="Compute basic binary classification metrics.")
253
+ def classification_metrics(
254
+ y_true: List[int],
255
+ y_score: List[float],
256
+ threshold: float = 0.5,
257
+ ) -> Dict[str, Any]:
258
+ """Compute accuracy, precision, recall, and F1 for binary predictions.
259
+
260
+ Parameters
261
+ ----------
262
+ y_true: list[int]
263
+ Ground-truth binary labels (0 or 1).
264
+ y_score: list[float]
265
+ Predicted scores or probabilities.
266
+ threshold: float
267
+ Decision threshold converting scores to class labels.
268
+ """
269
+ if np is None:
270
+ return _response(False, result=None, error="numpy is not available")
271
+
272
+ if len(y_true) != len(y_score):
273
+ return _response(False, result=None, error="y_true and y_score must have the same length")
274
+
275
+ if not y_true:
276
+ return _response(False, result=None, error="input arrays cannot be empty")
277
+
278
  try:
279
+ y_true_arr = np.array(y_true, dtype=int)
280
+ y_pred_arr = (np.array(y_score, dtype=float) >= threshold).astype(int)
281
+
282
+ tp = int(np.sum((y_true_arr == 1) & (y_pred_arr == 1)))
283
+ tn = int(np.sum((y_true_arr == 0) & (y_pred_arr == 0)))
284
+ fp = int(np.sum((y_true_arr == 0) & (y_pred_arr == 1)))
285
+ fn = int(np.sum((y_true_arr == 1) & (y_pred_arr == 0)))
286
+
287
+ total = len(y_true_arr)
288
+ accuracy = float((tp + tn) / total)
289
+ precision = float(tp / (tp + fp)) if (tp + fp) > 0 else 0.0
290
+ recall = float(tp / (tp + fn)) if (tp + fn) > 0 else 0.0
291
+ f1 = float(2 * precision * recall / (precision + recall)) if (precision + recall) > 0 else 0.0
292
+
293
+ return _response(
294
+ True,
295
+ result={
296
+ "threshold": threshold,
297
+ "confusion_matrix": {"tp": tp, "tn": tn, "fp": fp, "fn": fn},
298
+ "metrics": {
299
+ "accuracy": accuracy,
300
+ "precision": precision,
301
+ "recall": recall,
302
+ "f1": f1,
303
+ },
304
+ },
305
+ error=None,
306
+ )
307
+ except Exception as exc:
308
+ return _response(False, result=None, error=str(exc))
309
+
310
+
311
+ @mcp.tool(name="load_molnet_dataset", description="Load a DeepChem MolNet dataset by loader name.")
312
+ def load_molnet_dataset(
313
+ dataset_name: str,
314
+ featurizer: str = "ECFP",
315
+ splitter: str = "random",
316
+ reload: bool = False,
317
+ ) -> Dict[str, Any]:
318
+ """Load a MolNet dataset from DeepChem.
319
+
320
+ Parameters
321
+ ----------
322
+ dataset_name: str
323
+ MolNet dataset key, for example ``delaney`` or ``tox21``.
324
+ featurizer: str
325
+ Featurizer string accepted by the corresponding MolNet loader.
326
+ splitter: str
327
+ Split strategy accepted by the loader.
328
+ reload: bool
329
+ Whether to use/reuse cached dataset artifacts.
330
+ """
331
+ if dc is None:
332
+ return _response(False, result=None, error="deepchem is not available")
333
+
334
  try:
335
+ loader_name = f"load_{dataset_name.strip().lower()}"
336
+ loader = getattr(dc.molnet, loader_name, None)
337
+ if loader is None:
338
+ return _response(False, result=None, error=f"MolNet loader not found: {loader_name}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
339
 
340
+ tasks, datasets, transformers = loader(
341
+ featurizer=featurizer,
342
+ splitter=splitter,
343
+ reload=reload,
344
+ )
345
+
346
+ train_dataset, valid_dataset, test_dataset = datasets
347
+
348
+ return _response(
349
+ True,
350
+ result={
351
+ "loader": loader_name,
352
+ "n_tasks": len(tasks),
353
+ "task_sample": tasks[:10],
354
+ "dataset_sizes": {
355
+ "train": int(len(train_dataset)),
356
+ "valid": int(len(valid_dataset)),
357
+ "test": int(len(test_dataset)),
358
+ },
359
+ "n_transformers": len(transformers),
360
+ },
361
+ error=None,
362
+ )
363
+ except Exception as exc:
364
+ return _response(False, result=None, error=str(exc))
365
 
366
 
367
  def create_app():
368
+ """Create and return the module-level FastMCP app instance."""
369
  return mcp
370
 
371
+
372
  if __name__ == "__main__":
373
+ mcp.run()
deepchem/mcp_output/requirements.txt CHANGED
@@ -1,13 +1,6 @@
1
- fastmcp
2
- fastapi
3
- uvicorn[standard]
4
- pydantic>=2.0.0
5
- joblib
6
- numpy<2
7
  pandas
8
  scikit-learn
9
- sympy
10
- scipy>=1.10.1
11
- rdkit
12
- numpy
13
- tqdm
 
1
+ fastmcp>=2.0.0
2
+ deepchem
3
+ numpy
 
 
 
4
  pandas
5
  scikit-learn
6
+ rdkit-pypi
 
 
 
 
deepchem/mcp_output/start_mcp.py CHANGED
@@ -1,30 +1,45 @@
 
1
 
2
- """
3
- MCP Service Startup Entry
4
- """
5
- import sys
6
  import os
 
 
7
 
8
- project_root = os.path.dirname(os.path.abspath(__file__))
9
- mcp_plugin_dir = os.path.join(project_root, "mcp_plugin")
10
- if mcp_plugin_dir not in sys.path:
11
- sys.path.insert(0, mcp_plugin_dir)
12
 
13
  from mcp_service import create_app
14
 
15
- def main():
16
- """Start FastMCP service"""
 
 
 
 
 
 
 
 
 
 
17
  app = create_app()
18
- # Use environment variable to configure port, default 8000
19
- port = int(os.environ.get("MCP_PORT", "8000"))
20
-
21
- # Choose transport mode based on environment variable
22
- transport = os.environ.get("MCP_TRANSPORT", "stdio")
 
 
 
23
  if transport == "http":
24
- app.run(transport="http", host="0.0.0.0", port=port)
25
- else:
26
- # Default to STDIO mode
27
- app.run()
 
 
 
 
28
 
29
  if __name__ == "__main__":
30
  main()
 
1
+ from __future__ import annotations
2
 
 
 
 
 
3
  import os
4
+ import sys
5
+ from pathlib import Path
6
 
7
+ PLUGIN_DIR = (Path(__file__).resolve().parent / "mcp_plugin").resolve()
8
+ if str(PLUGIN_DIR) not in sys.path:
9
+ sys.path.insert(0, str(PLUGIN_DIR))
 
10
 
11
  from mcp_service import create_app
12
 
13
+
14
+ def _safe_port(value: str, default: int = 8000) -> int:
15
+ try:
16
+ return int(value)
17
+ except Exception:
18
+ return default
19
+
20
+
21
+ def main() -> None:
22
+ transport = os.getenv("MCP_TRANSPORT", "stdio").strip().lower()
23
+ port = _safe_port(os.getenv("MCP_PORT", "8000"), default=8000)
24
+
25
  app = create_app()
26
+
27
+ if transport == "stdio":
28
+ try:
29
+ app.run(transport="stdio")
30
+ except TypeError:
31
+ app.run()
32
+ return
33
+
34
  if transport == "http":
35
+ try:
36
+ app.run(transport="http", host="0.0.0.0", port=port)
37
+ except TypeError:
38
+ app.run(transport="http", port=port)
39
+ return
40
+
41
+ raise ValueError(f"Unsupported MCP_TRANSPORT: {transport}. Use 'stdio' or 'http'.")
42
+
43
 
44
  if __name__ == "__main__":
45
  main()
port.json CHANGED
@@ -1,5 +1 @@
1
- {
2
- "repo": "deepchem",
3
- "port": 7928,
4
- "timestamp": 1773460115
5
- }
 
1
+ {"port": 7860}
 
 
 
 
requirements.txt CHANGED
@@ -1,13 +1,8 @@
1
- fastmcp
2
- fastapi
3
- uvicorn[standard]
4
- pydantic>=2.0.0
5
- joblib
6
- numpy<2
7
  pandas
8
  scikit-learn
9
- sympy
10
- scipy>=1.10.1
11
- rdkit
12
- numpy
13
- tqdm
 
1
+ fastmcp>=2.0.0
2
+ deepchem
3
+ numpy
 
 
 
4
  pandas
5
  scikit-learn
6
+ rdkit-pypi
7
+ fastapi
8
+ uvicorn
 
 
run_docker.ps1 CHANGED
@@ -1,26 +1,10 @@
1
- cd $PSScriptRoot
2
  $ErrorActionPreference = "Stop"
3
- $entryName = if ($env:MCP_ENTRY_NAME) { $env:MCP_ENTRY_NAME } else { "deepchem" }
4
- $entryUrl = if ($env:MCP_ENTRY_URL) { $env:MCP_ENTRY_URL } else { "http://localhost:7928/mcp" }
5
- $imageName = if ($env:MCP_IMAGE_NAME) { $env:MCP_IMAGE_NAME } else { "deepchem-mcp" }
6
- $mcpDir = Join-Path $env:USERPROFILE ".cursor"
7
- $mcpPath = Join-Path $mcpDir "mcp.json"
8
- if (!(Test-Path $mcpDir)) { New-Item -ItemType Directory -Path $mcpDir | Out-Null }
9
- $config = @{}
10
- if (Test-Path $mcpPath) {
11
- try { $config = Get-Content $mcpPath -Raw | ConvertFrom-Json } catch { $config = @{} }
12
- }
13
- $serversOrdered = [ordered]@{}
14
- if ($config -and ($config.PSObject.Properties.Name -contains "mcpServers") -and $config.mcpServers) {
15
- $existing = $config.mcpServers
16
- if ($existing -is [pscustomobject]) {
17
- foreach ($p in $existing.PSObject.Properties) { if ($p.Name -ne $entryName) { $serversOrdered[$p.Name] = $p.Value } }
18
- } elseif ($existing -is [System.Collections.IDictionary]) {
19
- foreach ($k in $existing.Keys) { if ($k -ne $entryName) { $serversOrdered[$k] = $existing[$k] } }
20
- }
21
- }
22
- $serversOrdered[$entryName] = @{ url = $entryUrl }
23
- $config = @{ mcpServers = $serversOrdered }
24
- $config | ConvertTo-Json -Depth 10 | Set-Content -Path $mcpPath -Encoding UTF8
25
- docker build -t $imageName .
26
- docker run --rm -p 7928:7860 $imageName
 
 
1
  $ErrorActionPreference = "Stop"
2
+
3
+ $ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
4
+ $PortConfig = Get-Content -Raw (Join-Path $ScriptDir "port.json") | ConvertFrom-Json
5
+ $Port = [int]$PortConfig.port
6
+ $ImageName = "deepchem-mcp"
7
+
8
+ Set-Location $ScriptDir
9
+ docker build -t $ImageName .
10
+ docker run --rm -p "${Port}:7860" $ImageName
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
run_docker.sh CHANGED
@@ -1,75 +1,10 @@
1
  #!/usr/bin/env bash
2
  set -euo pipefail
3
- cd "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
4
- mcp_entry_name="${MCP_ENTRY_NAME:-deepchem}"
5
- mcp_entry_url="${MCP_ENTRY_URL:-http://localhost:7928/mcp}"
6
- mcp_dir="${HOME}/.cursor"
7
- mcp_path="${mcp_dir}/mcp.json"
8
- mkdir -p "${mcp_dir}"
9
- if command -v python3 >/dev/null 2>&1; then
10
- python3 - "${mcp_path}" "${mcp_entry_name}" "${mcp_entry_url}" <<'PY'
11
- import json, os, sys
12
- path, name, url = sys.argv[1:4]
13
- cfg = {"mcpServers": {}}
14
- if os.path.exists(path):
15
- try:
16
- with open(path, "r", encoding="utf-8") as f:
17
- cfg = json.load(f)
18
- except Exception:
19
- cfg = {"mcpServers": {}}
20
- if not isinstance(cfg, dict):
21
- cfg = {"mcpServers": {}}
22
- servers = cfg.get("mcpServers")
23
- if not isinstance(servers, dict):
24
- servers = {}
25
- ordered = {}
26
- for k, v in servers.items():
27
- if k != name:
28
- ordered[k] = v
29
- ordered[name] = {"url": url}
30
- cfg = {"mcpServers": ordered}
31
- with open(path, "w", encoding="utf-8") as f:
32
- json.dump(cfg, f, indent=2, ensure_ascii=False)
33
- PY
34
- elif command -v python >/dev/null 2>&1; then
35
- python - "${mcp_path}" "${mcp_entry_name}" "${mcp_entry_url}" <<'PY'
36
- import json, os, sys
37
- path, name, url = sys.argv[1:4]
38
- cfg = {"mcpServers": {}}
39
- if os.path.exists(path):
40
- try:
41
- with open(path, "r", encoding="utf-8") as f:
42
- cfg = json.load(f)
43
- except Exception:
44
- cfg = {"mcpServers": {}}
45
- if not isinstance(cfg, dict):
46
- cfg = {"mcpServers": {}}
47
- servers = cfg.get("mcpServers")
48
- if not isinstance(servers, dict):
49
- servers = {}
50
- ordered = {}
51
- for k, v in servers.items():
52
- if k != name:
53
- ordered[k] = v
54
- ordered[name] = {"url": url}
55
- cfg = {"mcpServers": ordered}
56
- with open(path, "w", encoding="utf-8") as f:
57
- json.dump(cfg, f, indent=2, ensure_ascii=False)
58
- PY
59
- elif command -v jq >/dev/null 2>&1; then
60
- name="${mcp_entry_name}"; url="${mcp_entry_url}"
61
- if [ -f "${mcp_path}" ]; then
62
- tmp="$(mktemp)"
63
- jq --arg name "$name" --arg url "$url" '
64
- .mcpServers = (.mcpServers // {})
65
- | .mcpServers as $s
66
- | ($s | with_entries(select(.key != $name))) as $base
67
- | .mcpServers = ($base + {($name): {"url": $url}})
68
- ' "${mcp_path}" > "${tmp}" && mv "${tmp}" "${mcp_path}"
69
- else
70
- printf '{ "mcpServers": { "%s": { "url": "%s" } } }
71
- ' "$name" "$url" > "${mcp_path}"
72
- fi
73
- fi
74
- docker build -t deepchem-mcp .
75
- docker run --rm -p 7928:7860 deepchem-mcp
 
1
  #!/usr/bin/env bash
2
  set -euo pipefail
3
+
4
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
5
+ PORT="$(python3 -c 'import json,sys; print(json.load(open(sys.argv[1], "r", encoding="utf-8"))["port"])' "$SCRIPT_DIR/port.json")"
6
+ IMAGE_NAME="deepchem-mcp"
7
+
8
+ cd "$SCRIPT_DIR"
9
+ docker build -t "$IMAGE_NAME" .
10
+ docker run --rm -p "${PORT}:7860" "$IMAGE_NAME"