modulector-sdk 2.3.0__tar.gz → 2.5.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {modulector_sdk-2.3.0/src/modulector_sdk.egg-info → modulector_sdk-2.5.2}/PKG-INFO +42 -1
- modulector_sdk-2.5.2/README.md +84 -0
- {modulector_sdk-2.3.0 → modulector_sdk-2.5.2}/pyproject.toml +5 -1
- modulector_sdk-2.5.2/src/modulector_sdk/mcp_server.py +538 -0
- {modulector_sdk-2.3.0 → modulector_sdk-2.5.2}/src/modulector_sdk/utils.py +10 -3
- {modulector_sdk-2.3.0 → modulector_sdk-2.5.2/src/modulector_sdk.egg-info}/PKG-INFO +42 -1
- {modulector_sdk-2.3.0 → modulector_sdk-2.5.2}/src/modulector_sdk.egg-info/SOURCES.txt +2 -0
- modulector_sdk-2.5.2/src/modulector_sdk.egg-info/entry_points.txt +2 -0
- {modulector_sdk-2.3.0 → modulector_sdk-2.5.2}/src/modulector_sdk.egg-info/requires.txt +1 -0
- modulector_sdk-2.3.0/README.md +0 -44
- {modulector_sdk-2.3.0 → modulector_sdk-2.5.2}/LICENSE +0 -0
- {modulector_sdk-2.3.0 → modulector_sdk-2.5.2}/setup.cfg +0 -0
- {modulector_sdk-2.3.0 → modulector_sdk-2.5.2}/src/modulector_sdk/__init__.py +0 -0
- {modulector_sdk-2.3.0 → modulector_sdk-2.5.2}/src/modulector_sdk/py.typed +0 -0
- {modulector_sdk-2.3.0 → modulector_sdk-2.5.2}/src/modulector_sdk/services.py +0 -0
- {modulector_sdk-2.3.0 → modulector_sdk-2.5.2}/src/modulector_sdk.egg-info/dependency_links.txt +0 -0
- {modulector_sdk-2.3.0 → modulector_sdk-2.5.2}/src/modulector_sdk.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: modulector-sdk
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.5.2
|
|
4
4
|
Summary: Typed Python SDK for the Modulector API.
|
|
5
5
|
Author: omicsdatascience
|
|
6
6
|
License-Expression: MIT
|
|
@@ -25,6 +25,7 @@ Classifier: Typing :: Typed
|
|
|
25
25
|
Requires-Python: >=3.10
|
|
26
26
|
Description-Content-Type: text/markdown
|
|
27
27
|
License-File: LICENSE
|
|
28
|
+
Requires-Dist: mcp==1.27.2
|
|
28
29
|
Requires-Dist: requests==2.31.0
|
|
29
30
|
Dynamic: license-file
|
|
30
31
|
|
|
@@ -63,6 +64,46 @@ MODULECTOR_API_BASE_URL=https://your-modulector.example.org python script.py
|
|
|
63
64
|
Every service function also accepts a `base_url` keyword argument for
|
|
64
65
|
per-request overrides.
|
|
65
66
|
|
|
67
|
+
## MCP server
|
|
68
|
+
|
|
69
|
+
The SDK also installs a Model Context Protocol server that exposes the same
|
|
70
|
+
service functions as LLM-callable tools.
|
|
71
|
+
|
|
72
|
+
For local stdio MCP clients:
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
modulector-mcp
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Example client configuration:
|
|
79
|
+
|
|
80
|
+
```json
|
|
81
|
+
{
|
|
82
|
+
"mcpServers": {
|
|
83
|
+
"modulector": {
|
|
84
|
+
"command": "modulector-mcp"
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
For Streamable HTTP clients:
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
modulector-mcp --transport streamable-http --host 127.0.0.1 --port 8000
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Then connect the client to `http://127.0.0.1:8000/mcp`.
|
|
97
|
+
|
|
98
|
+
The server includes tools for miRNA target interactions, miRNA details and
|
|
99
|
+
aliases, miRNA identifier resolution, methylation site lookup and annotation,
|
|
100
|
+
disease associations, drug associations, and PubMed news subscriptions. Use
|
|
101
|
+
`MODULECTOR_API_BASE_URL` or each tool's `base_url` argument to target a custom
|
|
102
|
+
Modulector deployment.
|
|
103
|
+
|
|
104
|
+
When deployed with the root Docker Compose stack, nginx proxies the MCP
|
|
105
|
+
Streamable HTTP endpoint at `https://<mydomain.com>/mcp`.
|
|
106
|
+
|
|
66
107
|
## Development
|
|
67
108
|
|
|
68
109
|
Build this SDK from the `sdk/` directory:
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# Modulector SDK
|
|
2
|
+
|
|
3
|
+
Typed Python SDK for the Modulector API. The package contains client helpers for
|
|
4
|
+
querying miRNA target interactions, miRNA aliases, methylation sites, disease
|
|
5
|
+
associations, drug associations, and PubMed subscriptions without installing the
|
|
6
|
+
Django backend dependencies.
|
|
7
|
+
|
|
8
|
+
## Installation
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
pip install modulector-sdk
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Usage
|
|
15
|
+
|
|
16
|
+
```python
|
|
17
|
+
from modulector_sdk import get_mirna_details, get_mirna_target_interactions
|
|
18
|
+
|
|
19
|
+
details = get_mirna_details("hsa-miR-21-5p")
|
|
20
|
+
interactions = get_mirna_target_interactions(
|
|
21
|
+
mirna="hsa-miR-21-5p",
|
|
22
|
+
gene="PTEN",
|
|
23
|
+
include_pubmeds=True,
|
|
24
|
+
)
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Set `MODULECTOR_API_BASE_URL` to target a different Modulector deployment:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
MODULECTOR_API_BASE_URL=https://your-modulector.example.org python script.py
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Every service function also accepts a `base_url` keyword argument for
|
|
34
|
+
per-request overrides.
|
|
35
|
+
|
|
36
|
+
## MCP server
|
|
37
|
+
|
|
38
|
+
The SDK also installs a Model Context Protocol server that exposes the same
|
|
39
|
+
service functions as LLM-callable tools.
|
|
40
|
+
|
|
41
|
+
For local stdio MCP clients:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
modulector-mcp
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Example client configuration:
|
|
48
|
+
|
|
49
|
+
```json
|
|
50
|
+
{
|
|
51
|
+
"mcpServers": {
|
|
52
|
+
"modulector": {
|
|
53
|
+
"command": "modulector-mcp"
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
For Streamable HTTP clients:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
modulector-mcp --transport streamable-http --host 127.0.0.1 --port 8000
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Then connect the client to `http://127.0.0.1:8000/mcp`.
|
|
66
|
+
|
|
67
|
+
The server includes tools for miRNA target interactions, miRNA details and
|
|
68
|
+
aliases, miRNA identifier resolution, methylation site lookup and annotation,
|
|
69
|
+
disease associations, drug associations, and PubMed news subscriptions. Use
|
|
70
|
+
`MODULECTOR_API_BASE_URL` or each tool's `base_url` argument to target a custom
|
|
71
|
+
Modulector deployment.
|
|
72
|
+
|
|
73
|
+
When deployed with the root Docker Compose stack, nginx proxies the MCP
|
|
74
|
+
Streamable HTTP endpoint at `https://<mydomain.com>/mcp`.
|
|
75
|
+
|
|
76
|
+
## Development
|
|
77
|
+
|
|
78
|
+
Build this SDK from the `sdk/` directory:
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
uv build
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
The generated source distribution and wheel are written to `sdk/dist/`.
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "modulector-sdk"
|
|
7
|
-
version = "2.
|
|
7
|
+
version = "2.5.2"
|
|
8
8
|
description = "Typed Python SDK for the Modulector API."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -36,6 +36,7 @@ classifiers = [
|
|
|
36
36
|
"Typing :: Typed",
|
|
37
37
|
]
|
|
38
38
|
dependencies = [
|
|
39
|
+
"mcp==1.27.2",
|
|
39
40
|
"requests==2.31.0",
|
|
40
41
|
]
|
|
41
42
|
|
|
@@ -45,6 +46,9 @@ Homepage = "https://modulector.multiomix.org/"
|
|
|
45
46
|
Issues = "https://github.com/omics-datascience/modulector/issues"
|
|
46
47
|
Repository = "https://github.com/omics-datascience/modulector"
|
|
47
48
|
|
|
49
|
+
[project.scripts]
|
|
50
|
+
modulector-mcp = "modulector_sdk.mcp_server:main"
|
|
51
|
+
|
|
48
52
|
[tool.setuptools.packages.find]
|
|
49
53
|
where = ["src"]
|
|
50
54
|
|
|
@@ -0,0 +1,538 @@
|
|
|
1
|
+
"""MCP server exposing Modulector SDK services to LLM clients."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import os
|
|
7
|
+
from typing import Any, Final, Literal, TypeVar, cast
|
|
8
|
+
from urllib.parse import urlparse
|
|
9
|
+
|
|
10
|
+
from mcp.server.fastmcp import FastMCP
|
|
11
|
+
from mcp.server.transport_security import TransportSecuritySettings
|
|
12
|
+
|
|
13
|
+
from . import services
|
|
14
|
+
from .utils import PaginatedResponse
|
|
15
|
+
|
|
16
|
+
T = TypeVar("T")
|
|
17
|
+
Transport = Literal["stdio", "sse", "streamable-http"]
|
|
18
|
+
|
|
19
|
+
SERVER_INSTRUCTIONS: Final[str] = (
|
|
20
|
+
"Use Modulector to help researchers inspect human miRNA target "
|
|
21
|
+
"interactions, miRNA aliases, disease and drug associations, and "
|
|
22
|
+
"Illumina methylation site annotations. Prefer finder tools when the "
|
|
23
|
+
"user gives partial or uncertain identifiers, then use the detail or "
|
|
24
|
+
"paginated tools with explicit identifiers. The returned data comes from "
|
|
25
|
+
"the configured Modulector API deployment."
|
|
26
|
+
)
|
|
27
|
+
PUBLIC_BASE_URL: Final[str] = os.getenv(
|
|
28
|
+
"MODULECTOR_PUBLIC_BASE_URL",
|
|
29
|
+
services.MODULECTOR_API_BASE_URL,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
mcp = FastMCP(
|
|
33
|
+
name="modulector",
|
|
34
|
+
instructions=SERVER_INSTRUCTIONS,
|
|
35
|
+
website_url=PUBLIC_BASE_URL,
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@mcp.resource(
|
|
40
|
+
"modulector://about",
|
|
41
|
+
title="About Modulector",
|
|
42
|
+
description="Overview of the Modulector data exposed by this MCP server.",
|
|
43
|
+
mime_type="text/markdown",
|
|
44
|
+
)
|
|
45
|
+
def about_modulector() -> str:
|
|
46
|
+
"""Return a concise overview of this MCP server.
|
|
47
|
+
|
|
48
|
+
:return: Markdown overview for LLM context.
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
return (
|
|
52
|
+
"# Modulector\n\n"
|
|
53
|
+
"Modulector provides programmatic access to curated human miRNA target "
|
|
54
|
+
"interactions, miRNA aliases, miRNA-disease associations, "
|
|
55
|
+
"miRNA-drug associations, and Illumina Infinium MethylationEPIC 2.0 "
|
|
56
|
+
"site annotations.\n\n"
|
|
57
|
+
f"Default API base URL: `{services.MODULECTOR_API_BASE_URL}`\n\n"
|
|
58
|
+
f"Public website URL: `{PUBLIC_BASE_URL}`\n\n"
|
|
59
|
+
"Use the finder tools for partial identifiers, then query detail or "
|
|
60
|
+
"paginated tools with exact miRNA, gene, disease, drug, or methylation "
|
|
61
|
+
"site filters."
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@mcp.prompt(
|
|
66
|
+
name="modulector_research_plan",
|
|
67
|
+
title="Modulector Research Plan",
|
|
68
|
+
description="Guide an LLM through a focused Modulector research lookup.",
|
|
69
|
+
)
|
|
70
|
+
def modulector_research_plan(research_question: str) -> str:
|
|
71
|
+
"""Return a prompt for planning a Modulector lookup.
|
|
72
|
+
|
|
73
|
+
:param research_question: Research question to investigate.
|
|
74
|
+
:return: Prompt text that guides tool selection and result synthesis.
|
|
75
|
+
"""
|
|
76
|
+
|
|
77
|
+
return (
|
|
78
|
+
"Plan a Modulector lookup for this research question:\n\n"
|
|
79
|
+
f"{research_question}\n\n"
|
|
80
|
+
"First identify whether the task involves miRNA identifiers, gene "
|
|
81
|
+
"symbols, diseases, drugs, or methylation sites. Use finder or alias "
|
|
82
|
+
"tools to normalize uncertain identifiers. Then retrieve the smallest "
|
|
83
|
+
"useful page of records, include PubMed links only when needed, and "
|
|
84
|
+
"summarize the evidence with clear caveats about database scope."
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
@mcp.tool()
|
|
89
|
+
def get_mirna_target_interactions(
|
|
90
|
+
mirna: str | None = None,
|
|
91
|
+
gene: str | None = None,
|
|
92
|
+
score: float | None = None,
|
|
93
|
+
include_pubmeds: bool | None = None,
|
|
94
|
+
ordering: str | None = None,
|
|
95
|
+
search: str | None = None,
|
|
96
|
+
page: int | None = None,
|
|
97
|
+
page_size: int | None = None,
|
|
98
|
+
base_url: str | None = None,
|
|
99
|
+
timeout: float = 30.0,
|
|
100
|
+
) -> dict[str, Any]:
|
|
101
|
+
"""Return miRNA-gene target interactions from Modulector.
|
|
102
|
+
|
|
103
|
+
:param mirna: miRNA accession ID or miRBase mature name.
|
|
104
|
+
:param gene: Gene symbol.
|
|
105
|
+
:param score: Minimum MirDIP score between 0 and 1.
|
|
106
|
+
:param include_pubmeds: Whether to include PubMed URLs for interactions.
|
|
107
|
+
:param ordering: Comma-separated ordering fields, such as ``-score,gene``.
|
|
108
|
+
:param search: Search term for supported server-side fields.
|
|
109
|
+
:param page: Page number to request.
|
|
110
|
+
:param page_size: Number of records to request.
|
|
111
|
+
:param base_url: Optional Modulector API deployment URL.
|
|
112
|
+
:param timeout: Request timeout in seconds.
|
|
113
|
+
:return: Paginated interaction records.
|
|
114
|
+
"""
|
|
115
|
+
|
|
116
|
+
return _page_to_dict(
|
|
117
|
+
services.get_mirna_target_interactions(
|
|
118
|
+
base_url=_base_url(base_url),
|
|
119
|
+
mirna=mirna,
|
|
120
|
+
gene=gene,
|
|
121
|
+
score=score,
|
|
122
|
+
include_pubmeds=include_pubmeds,
|
|
123
|
+
ordering=ordering,
|
|
124
|
+
search=search,
|
|
125
|
+
page=page,
|
|
126
|
+
page_size=page_size,
|
|
127
|
+
timeout=timeout,
|
|
128
|
+
)
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
@mcp.tool()
|
|
133
|
+
def get_mirna_details(
|
|
134
|
+
mirna: str,
|
|
135
|
+
base_url: str | None = None,
|
|
136
|
+
timeout: float = 30.0,
|
|
137
|
+
) -> dict[str, Any]:
|
|
138
|
+
"""Return sequence, aliases, accession, and source links for one miRNA.
|
|
139
|
+
|
|
140
|
+
:param mirna: miRNA identifier, such as a mature miRNA ID or accession ID.
|
|
141
|
+
:param base_url: Optional Modulector API deployment URL.
|
|
142
|
+
:param timeout: Request timeout in seconds.
|
|
143
|
+
:return: miRNA detail record.
|
|
144
|
+
"""
|
|
145
|
+
|
|
146
|
+
return cast(
|
|
147
|
+
dict[str, Any],
|
|
148
|
+
services.get_mirna_details(
|
|
149
|
+
mirna,
|
|
150
|
+
base_url=_base_url(base_url),
|
|
151
|
+
timeout=timeout,
|
|
152
|
+
),
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
@mcp.tool()
|
|
157
|
+
def get_mirna_aliases(
|
|
158
|
+
mature_mirna: str | None = None,
|
|
159
|
+
mirbase_accession_id: str | None = None,
|
|
160
|
+
previous_mature_mirna: str | None = None,
|
|
161
|
+
search: str | None = None,
|
|
162
|
+
ordering: str | None = None,
|
|
163
|
+
page: int | None = None,
|
|
164
|
+
page_size: int | None = None,
|
|
165
|
+
base_url: str | None = None,
|
|
166
|
+
timeout: float = 30.0,
|
|
167
|
+
) -> dict[str, Any]:
|
|
168
|
+
"""Return miRBase accession and mature miRNA alias records.
|
|
169
|
+
|
|
170
|
+
:param mature_mirna: Exact mature miRNA filter.
|
|
171
|
+
:param mirbase_accession_id: Exact miRBase accession ID filter.
|
|
172
|
+
:param previous_mature_mirna: Exact previous mature miRNA filter.
|
|
173
|
+
:param search: Search across mature IDs, previous IDs, and accessions.
|
|
174
|
+
:param ordering: Comma-separated ordering fields.
|
|
175
|
+
:param page: Page number to request.
|
|
176
|
+
:param page_size: Number of records to request.
|
|
177
|
+
:param base_url: Optional Modulector API deployment URL.
|
|
178
|
+
:param timeout: Request timeout in seconds.
|
|
179
|
+
:return: Paginated miRNA alias records.
|
|
180
|
+
"""
|
|
181
|
+
|
|
182
|
+
return _page_to_dict(
|
|
183
|
+
services.get_mirna_aliases(
|
|
184
|
+
base_url=_base_url(base_url),
|
|
185
|
+
mature_mirna=mature_mirna,
|
|
186
|
+
mirbase_accession_id=mirbase_accession_id,
|
|
187
|
+
previous_mature_mirna=previous_mature_mirna,
|
|
188
|
+
search=search,
|
|
189
|
+
ordering=ordering,
|
|
190
|
+
page=page,
|
|
191
|
+
page_size=page_size,
|
|
192
|
+
timeout=timeout,
|
|
193
|
+
)
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
@mcp.tool()
|
|
198
|
+
def find_mirna_codes(
|
|
199
|
+
query: str,
|
|
200
|
+
limit: int | None = None,
|
|
201
|
+
base_url: str | None = None,
|
|
202
|
+
timeout: float = 30.0,
|
|
203
|
+
) -> list[str]:
|
|
204
|
+
"""Return miRNA identifiers matching a partial search string.
|
|
205
|
+
|
|
206
|
+
:param query: miRNA search string.
|
|
207
|
+
:param limit: Maximum number of identifiers to return.
|
|
208
|
+
:param base_url: Optional Modulector API deployment URL.
|
|
209
|
+
:param timeout: Request timeout in seconds.
|
|
210
|
+
:return: Matching miRNA IDs or accession IDs.
|
|
211
|
+
"""
|
|
212
|
+
|
|
213
|
+
return services.find_mirna_codes(
|
|
214
|
+
query,
|
|
215
|
+
base_url=_base_url(base_url),
|
|
216
|
+
limit=limit,
|
|
217
|
+
timeout=timeout,
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
@mcp.tool()
|
|
222
|
+
def get_mirna_codes(
|
|
223
|
+
mirna_codes: list[str],
|
|
224
|
+
base_url: str | None = None,
|
|
225
|
+
timeout: float = 30.0,
|
|
226
|
+
) -> dict[str, str | None]:
|
|
227
|
+
"""Resolve miRNA identifiers to approved miRBase accessions.
|
|
228
|
+
|
|
229
|
+
:param mirna_codes: miRNA identifiers to resolve.
|
|
230
|
+
:param base_url: Optional Modulector API deployment URL.
|
|
231
|
+
:param timeout: Request timeout in seconds.
|
|
232
|
+
:return: Mapping from requested identifiers to accession IDs or null.
|
|
233
|
+
"""
|
|
234
|
+
|
|
235
|
+
return services.get_mirna_codes(
|
|
236
|
+
mirna_codes,
|
|
237
|
+
base_url=_base_url(base_url),
|
|
238
|
+
timeout=timeout,
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
@mcp.tool()
|
|
243
|
+
def find_methylation_sites(
|
|
244
|
+
query: str,
|
|
245
|
+
limit: int | None = None,
|
|
246
|
+
base_url: str | None = None,
|
|
247
|
+
timeout: float = 30.0,
|
|
248
|
+
) -> list[str]:
|
|
249
|
+
"""Return methylation site names matching a partial search string.
|
|
250
|
+
|
|
251
|
+
:param query: Methylation site search string.
|
|
252
|
+
:param limit: Maximum number of site names to return.
|
|
253
|
+
:param base_url: Optional Modulector API deployment URL.
|
|
254
|
+
:param timeout: Request timeout in seconds.
|
|
255
|
+
:return: Matching methylation site names.
|
|
256
|
+
"""
|
|
257
|
+
|
|
258
|
+
return services.find_methylation_sites(
|
|
259
|
+
query,
|
|
260
|
+
base_url=_base_url(base_url),
|
|
261
|
+
limit=limit,
|
|
262
|
+
timeout=timeout,
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
@mcp.tool()
|
|
267
|
+
def get_methylation_sites(
|
|
268
|
+
methylation_sites: list[str],
|
|
269
|
+
base_url: str | None = None,
|
|
270
|
+
timeout: float = 30.0,
|
|
271
|
+
) -> dict[str, list[str]]:
|
|
272
|
+
"""Resolve methylation identifiers to EPIC 2.0 site names.
|
|
273
|
+
|
|
274
|
+
:param methylation_sites: Illumina methylation site names or identifiers.
|
|
275
|
+
:param base_url: Optional Modulector API deployment URL.
|
|
276
|
+
:param timeout: Request timeout in seconds.
|
|
277
|
+
:return: Mapping from requested identifiers to current EPIC 2.0 names.
|
|
278
|
+
"""
|
|
279
|
+
|
|
280
|
+
return services.get_methylation_sites(
|
|
281
|
+
methylation_sites,
|
|
282
|
+
base_url=_base_url(base_url),
|
|
283
|
+
timeout=timeout,
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
@mcp.tool()
|
|
288
|
+
def get_methylation_site_genes(
|
|
289
|
+
methylation_sites: list[str],
|
|
290
|
+
base_url: str | None = None,
|
|
291
|
+
timeout: float = 30.0,
|
|
292
|
+
) -> dict[str, list[str]]:
|
|
293
|
+
"""Return genes associated with methylation site identifiers.
|
|
294
|
+
|
|
295
|
+
:param methylation_sites: Illumina methylation site names or identifiers.
|
|
296
|
+
:param base_url: Optional Modulector API deployment URL.
|
|
297
|
+
:param timeout: Request timeout in seconds.
|
|
298
|
+
:return: Mapping from requested site identifiers to associated genes.
|
|
299
|
+
"""
|
|
300
|
+
|
|
301
|
+
return services.get_methylation_site_genes(
|
|
302
|
+
methylation_sites,
|
|
303
|
+
base_url=_base_url(base_url),
|
|
304
|
+
timeout=timeout,
|
|
305
|
+
)
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
@mcp.tool()
|
|
309
|
+
def get_methylation_details(
|
|
310
|
+
methylation_site: str,
|
|
311
|
+
base_url: str | None = None,
|
|
312
|
+
timeout: float = 30.0,
|
|
313
|
+
) -> dict[str, Any]:
|
|
314
|
+
"""Return detailed annotations for one methylation site.
|
|
315
|
+
|
|
316
|
+
:param methylation_site: Methylation site name from EPIC 2.0.
|
|
317
|
+
:param base_url: Optional Modulector API deployment URL.
|
|
318
|
+
:param timeout: Request timeout in seconds.
|
|
319
|
+
:return: Methylation site detail record.
|
|
320
|
+
"""
|
|
321
|
+
|
|
322
|
+
return cast(
|
|
323
|
+
dict[str, Any],
|
|
324
|
+
services.get_methylation_details(
|
|
325
|
+
methylation_site,
|
|
326
|
+
base_url=_base_url(base_url),
|
|
327
|
+
timeout=timeout,
|
|
328
|
+
),
|
|
329
|
+
)
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
@mcp.tool()
|
|
333
|
+
def get_diseases(
|
|
334
|
+
mirna: str | None = None,
|
|
335
|
+
search: str | None = None,
|
|
336
|
+
ordering: str | None = None,
|
|
337
|
+
page: int | None = None,
|
|
338
|
+
page_size: int | None = None,
|
|
339
|
+
base_url: str | None = None,
|
|
340
|
+
timeout: float = 30.0,
|
|
341
|
+
) -> dict[str, Any]:
|
|
342
|
+
"""Return miRNA and human disease association records.
|
|
343
|
+
|
|
344
|
+
:param mirna: miRNA code or accession ID filter.
|
|
345
|
+
:param search: Case-insensitive disease search term.
|
|
346
|
+
:param ordering: Comma-separated ordering fields.
|
|
347
|
+
:param page: Page number to request.
|
|
348
|
+
:param page_size: Number of records to request.
|
|
349
|
+
:param base_url: Optional Modulector API deployment URL.
|
|
350
|
+
:param timeout: Request timeout in seconds.
|
|
351
|
+
:return: Paginated disease association records.
|
|
352
|
+
"""
|
|
353
|
+
|
|
354
|
+
return _page_to_dict(
|
|
355
|
+
services.get_diseases(
|
|
356
|
+
base_url=_base_url(base_url),
|
|
357
|
+
mirna=mirna,
|
|
358
|
+
search=search,
|
|
359
|
+
ordering=ordering,
|
|
360
|
+
page=page,
|
|
361
|
+
page_size=page_size,
|
|
362
|
+
timeout=timeout,
|
|
363
|
+
)
|
|
364
|
+
)
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
@mcp.tool()
|
|
368
|
+
def get_drugs(
|
|
369
|
+
mirna: str | None = None,
|
|
370
|
+
fda_approved: bool | None = None,
|
|
371
|
+
search: str | None = None,
|
|
372
|
+
ordering: str | None = None,
|
|
373
|
+
page: int | None = None,
|
|
374
|
+
page_size: int | None = None,
|
|
375
|
+
base_url: str | None = None,
|
|
376
|
+
timeout: float = 30.0,
|
|
377
|
+
) -> dict[str, Any]:
|
|
378
|
+
"""Return drug or small molecule records affecting miRNA expression.
|
|
379
|
+
|
|
380
|
+
:param mirna: miRNA code or accession ID filter.
|
|
381
|
+
:param fda_approved: Optional FDA approval filter.
|
|
382
|
+
:param search: Search term for condition, molecule, or expression fields.
|
|
383
|
+
:param ordering: Comma-separated ordering fields.
|
|
384
|
+
:param page: Page number to request.
|
|
385
|
+
:param page_size: Number of records to request.
|
|
386
|
+
:param base_url: Optional Modulector API deployment URL.
|
|
387
|
+
:param timeout: Request timeout in seconds.
|
|
388
|
+
:return: Paginated drug association records.
|
|
389
|
+
"""
|
|
390
|
+
|
|
391
|
+
return _page_to_dict(
|
|
392
|
+
services.get_drugs(
|
|
393
|
+
base_url=_base_url(base_url),
|
|
394
|
+
mirna=mirna,
|
|
395
|
+
fda_approved=fda_approved,
|
|
396
|
+
search=search,
|
|
397
|
+
ordering=ordering,
|
|
398
|
+
page=page,
|
|
399
|
+
page_size=page_size,
|
|
400
|
+
timeout=timeout,
|
|
401
|
+
)
|
|
402
|
+
)
|
|
403
|
+
|
|
404
|
+
|
|
405
|
+
def main() -> None:
|
|
406
|
+
"""Run the Modulector MCP server.
|
|
407
|
+
|
|
408
|
+
:return: Nothing.
|
|
409
|
+
"""
|
|
410
|
+
|
|
411
|
+
args = _parse_args()
|
|
412
|
+
transport = cast(Transport, args.transport)
|
|
413
|
+
if transport != "stdio":
|
|
414
|
+
mcp.settings.host = args.host
|
|
415
|
+
mcp.settings.port = args.port
|
|
416
|
+
mcp.settings.json_response = args.json_response
|
|
417
|
+
_configure_http_transport_security()
|
|
418
|
+
mcp.run(transport=transport)
|
|
419
|
+
|
|
420
|
+
|
|
421
|
+
def _parse_args() -> argparse.Namespace:
|
|
422
|
+
"""Parse command-line arguments for the MCP server.
|
|
423
|
+
|
|
424
|
+
:return: Parsed command-line arguments.
|
|
425
|
+
"""
|
|
426
|
+
|
|
427
|
+
parser = argparse.ArgumentParser(
|
|
428
|
+
description="Run the Modulector MCP server.",
|
|
429
|
+
)
|
|
430
|
+
parser.add_argument(
|
|
431
|
+
"--transport",
|
|
432
|
+
choices=("stdio", "sse", "streamable-http"),
|
|
433
|
+
default="stdio",
|
|
434
|
+
help="MCP transport to use. Defaults to stdio.",
|
|
435
|
+
)
|
|
436
|
+
parser.add_argument(
|
|
437
|
+
"--host",
|
|
438
|
+
default="127.0.0.1",
|
|
439
|
+
help="Host for HTTP transports. Defaults to 127.0.0.1.",
|
|
440
|
+
)
|
|
441
|
+
parser.add_argument(
|
|
442
|
+
"--port",
|
|
443
|
+
type=int,
|
|
444
|
+
default=8000,
|
|
445
|
+
help="Port for HTTP transports. Defaults to 8000.",
|
|
446
|
+
)
|
|
447
|
+
parser.add_argument(
|
|
448
|
+
"--json-response",
|
|
449
|
+
action="store_true",
|
|
450
|
+
help="Use JSON responses for Streamable HTTP instead of SSE streams.",
|
|
451
|
+
)
|
|
452
|
+
return parser.parse_args()
|
|
453
|
+
|
|
454
|
+
|
|
455
|
+
def _base_url(base_url: str | None) -> str:
|
|
456
|
+
"""Return the requested or default Modulector API base URL.
|
|
457
|
+
|
|
458
|
+
:param base_url: Optional Modulector API deployment URL.
|
|
459
|
+
:return: API base URL.
|
|
460
|
+
"""
|
|
461
|
+
|
|
462
|
+
return base_url or services.MODULECTOR_API_BASE_URL
|
|
463
|
+
|
|
464
|
+
|
|
465
|
+
def _configure_http_transport_security() -> None:
|
|
466
|
+
"""Configure DNS rebinding protection for HTTP MCP transports.
|
|
467
|
+
|
|
468
|
+
:return: None.
|
|
469
|
+
"""
|
|
470
|
+
|
|
471
|
+
mcp.settings.transport_security = TransportSecuritySettings(
|
|
472
|
+
allowed_hosts=_allowed_hosts(),
|
|
473
|
+
allowed_origins=_allowed_origins(),
|
|
474
|
+
)
|
|
475
|
+
|
|
476
|
+
|
|
477
|
+
def _allowed_hosts() -> list[str]:
|
|
478
|
+
"""Return allowed Host headers for the MCP HTTP transport.
|
|
479
|
+
|
|
480
|
+
:return: Host header allow-list.
|
|
481
|
+
"""
|
|
482
|
+
|
|
483
|
+
configured_hosts = _csv_env("MODULECTOR_MCP_ALLOWED_HOSTS")
|
|
484
|
+
if configured_hosts:
|
|
485
|
+
return configured_hosts
|
|
486
|
+
|
|
487
|
+
parsed_public_url = urlparse(PUBLIC_BASE_URL)
|
|
488
|
+
public_host = parsed_public_url.netloc
|
|
489
|
+
hosts = ["127.0.0.1:*", "localhost:*", "[::1]:*"]
|
|
490
|
+
if public_host:
|
|
491
|
+
hosts.append(public_host)
|
|
492
|
+
return hosts
|
|
493
|
+
|
|
494
|
+
|
|
495
|
+
def _allowed_origins() -> list[str]:
|
|
496
|
+
"""Return allowed Origin headers for the MCP HTTP transport.
|
|
497
|
+
|
|
498
|
+
:return: Origin header allow-list.
|
|
499
|
+
"""
|
|
500
|
+
|
|
501
|
+
configured_origins = _csv_env("MODULECTOR_MCP_ALLOWED_ORIGINS")
|
|
502
|
+
if configured_origins:
|
|
503
|
+
return configured_origins
|
|
504
|
+
|
|
505
|
+
parsed_public_url = urlparse(PUBLIC_BASE_URL)
|
|
506
|
+
origins = ["http://127.0.0.1:*", "http://localhost:*", "http://[::1]:*"]
|
|
507
|
+
if parsed_public_url.scheme and parsed_public_url.netloc:
|
|
508
|
+
origins.append(f"{parsed_public_url.scheme}://{parsed_public_url.netloc}")
|
|
509
|
+
return origins
|
|
510
|
+
|
|
511
|
+
|
|
512
|
+
def _csv_env(name: str) -> list[str]:
|
|
513
|
+
"""Return comma-separated environment variable values.
|
|
514
|
+
|
|
515
|
+
:param name: Environment variable name.
|
|
516
|
+
:return: Non-empty stripped values.
|
|
517
|
+
"""
|
|
518
|
+
|
|
519
|
+
return [value.strip() for value in os.getenv(name, "").split(",") if value.strip()]
|
|
520
|
+
|
|
521
|
+
|
|
522
|
+
def _page_to_dict(page: PaginatedResponse[T]) -> dict[str, Any]:
|
|
523
|
+
"""Convert a paginated SDK response into a JSON-serializable mapping.
|
|
524
|
+
|
|
525
|
+
:param page: SDK paginated response.
|
|
526
|
+
:return: Mapping with pagination metadata and results.
|
|
527
|
+
"""
|
|
528
|
+
|
|
529
|
+
return {
|
|
530
|
+
"count": page.count,
|
|
531
|
+
"next": page.next,
|
|
532
|
+
"previous": page.previous,
|
|
533
|
+
"results": page.results,
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
|
|
537
|
+
if __name__ == "__main__":
|
|
538
|
+
main()
|
|
@@ -191,6 +191,15 @@ def _with_pagination_params(
|
|
|
191
191
|
page: int | None,
|
|
192
192
|
page_size: int | None,
|
|
193
193
|
) -> dict[str, Any] | None:
|
|
194
|
+
"""
|
|
195
|
+
Return a new params dict with pagination parameters added, or None if no params.
|
|
196
|
+
:param params: Parameters to include in the request, or None for no parameters.
|
|
197
|
+
:param page: Page number to include in the request, or None for no page parameter.
|
|
198
|
+
:param page_size: Number of items per page to include in the request, or None for no page_size parameter.
|
|
199
|
+
:raises ValueError: If page is less than 1.
|
|
200
|
+
:raises ValueError: If page_size is not between 1 and 1000.
|
|
201
|
+
:return: A new params dict with pagination parameters added, or None if no params.
|
|
202
|
+
"""
|
|
194
203
|
if page is not None and page < 1:
|
|
195
204
|
raise ValueError("page must be greater than 0")
|
|
196
205
|
if page_size is not None and not 1 <= page_size <= 1000:
|
|
@@ -225,9 +234,7 @@ def _parse_paginated_response(payload: JSON) -> PaginatedResponse[Any]:
|
|
|
225
234
|
if next_url is not None and not isinstance(next_url, str):
|
|
226
235
|
raise ValueError("paginated response field 'next' must be a string or null")
|
|
227
236
|
if previous_url is not None and not isinstance(previous_url, str):
|
|
228
|
-
raise ValueError(
|
|
229
|
-
"paginated response field 'previous' must be a string or null"
|
|
230
|
-
)
|
|
237
|
+
raise ValueError("paginated response field 'previous' must be a string or null")
|
|
231
238
|
|
|
232
239
|
return PaginatedResponse(
|
|
233
240
|
count=int(payload["count"]),
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: modulector-sdk
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.5.2
|
|
4
4
|
Summary: Typed Python SDK for the Modulector API.
|
|
5
5
|
Author: omicsdatascience
|
|
6
6
|
License-Expression: MIT
|
|
@@ -25,6 +25,7 @@ Classifier: Typing :: Typed
|
|
|
25
25
|
Requires-Python: >=3.10
|
|
26
26
|
Description-Content-Type: text/markdown
|
|
27
27
|
License-File: LICENSE
|
|
28
|
+
Requires-Dist: mcp==1.27.2
|
|
28
29
|
Requires-Dist: requests==2.31.0
|
|
29
30
|
Dynamic: license-file
|
|
30
31
|
|
|
@@ -63,6 +64,46 @@ MODULECTOR_API_BASE_URL=https://your-modulector.example.org python script.py
|
|
|
63
64
|
Every service function also accepts a `base_url` keyword argument for
|
|
64
65
|
per-request overrides.
|
|
65
66
|
|
|
67
|
+
## MCP server
|
|
68
|
+
|
|
69
|
+
The SDK also installs a Model Context Protocol server that exposes the same
|
|
70
|
+
service functions as LLM-callable tools.
|
|
71
|
+
|
|
72
|
+
For local stdio MCP clients:
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
modulector-mcp
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Example client configuration:
|
|
79
|
+
|
|
80
|
+
```json
|
|
81
|
+
{
|
|
82
|
+
"mcpServers": {
|
|
83
|
+
"modulector": {
|
|
84
|
+
"command": "modulector-mcp"
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
For Streamable HTTP clients:
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
modulector-mcp --transport streamable-http --host 127.0.0.1 --port 8000
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Then connect the client to `http://127.0.0.1:8000/mcp`.
|
|
97
|
+
|
|
98
|
+
The server includes tools for miRNA target interactions, miRNA details and
|
|
99
|
+
aliases, miRNA identifier resolution, methylation site lookup and annotation,
|
|
100
|
+
disease associations, drug associations, and PubMed news subscriptions. Use
|
|
101
|
+
`MODULECTOR_API_BASE_URL` or each tool's `base_url` argument to target a custom
|
|
102
|
+
Modulector deployment.
|
|
103
|
+
|
|
104
|
+
When deployed with the root Docker Compose stack, nginx proxies the MCP
|
|
105
|
+
Streamable HTTP endpoint at `https://<mydomain.com>/mcp`.
|
|
106
|
+
|
|
66
107
|
## Development
|
|
67
108
|
|
|
68
109
|
Build this SDK from the `sdk/` directory:
|
|
@@ -2,11 +2,13 @@ LICENSE
|
|
|
2
2
|
README.md
|
|
3
3
|
pyproject.toml
|
|
4
4
|
src/modulector_sdk/__init__.py
|
|
5
|
+
src/modulector_sdk/mcp_server.py
|
|
5
6
|
src/modulector_sdk/py.typed
|
|
6
7
|
src/modulector_sdk/services.py
|
|
7
8
|
src/modulector_sdk/utils.py
|
|
8
9
|
src/modulector_sdk.egg-info/PKG-INFO
|
|
9
10
|
src/modulector_sdk.egg-info/SOURCES.txt
|
|
10
11
|
src/modulector_sdk.egg-info/dependency_links.txt
|
|
12
|
+
src/modulector_sdk.egg-info/entry_points.txt
|
|
11
13
|
src/modulector_sdk.egg-info/requires.txt
|
|
12
14
|
src/modulector_sdk.egg-info/top_level.txt
|
modulector_sdk-2.3.0/README.md
DELETED
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
# Modulector SDK
|
|
2
|
-
|
|
3
|
-
Typed Python SDK for the Modulector API. The package contains client helpers for
|
|
4
|
-
querying miRNA target interactions, miRNA aliases, methylation sites, disease
|
|
5
|
-
associations, drug associations, and PubMed subscriptions without installing the
|
|
6
|
-
Django backend dependencies.
|
|
7
|
-
|
|
8
|
-
## Installation
|
|
9
|
-
|
|
10
|
-
```bash
|
|
11
|
-
pip install modulector-sdk
|
|
12
|
-
```
|
|
13
|
-
|
|
14
|
-
## Usage
|
|
15
|
-
|
|
16
|
-
```python
|
|
17
|
-
from modulector_sdk import get_mirna_details, get_mirna_target_interactions
|
|
18
|
-
|
|
19
|
-
details = get_mirna_details("hsa-miR-21-5p")
|
|
20
|
-
interactions = get_mirna_target_interactions(
|
|
21
|
-
mirna="hsa-miR-21-5p",
|
|
22
|
-
gene="PTEN",
|
|
23
|
-
include_pubmeds=True,
|
|
24
|
-
)
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
Set `MODULECTOR_API_BASE_URL` to target a different Modulector deployment:
|
|
28
|
-
|
|
29
|
-
```bash
|
|
30
|
-
MODULECTOR_API_BASE_URL=https://your-modulector.example.org python script.py
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
Every service function also accepts a `base_url` keyword argument for
|
|
34
|
-
per-request overrides.
|
|
35
|
-
|
|
36
|
-
## Development
|
|
37
|
-
|
|
38
|
-
Build this SDK from the `sdk/` directory:
|
|
39
|
-
|
|
40
|
-
```bash
|
|
41
|
-
uv build
|
|
42
|
-
```
|
|
43
|
-
|
|
44
|
-
The generated source distribution and wheel are written to `sdk/dist/`.
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{modulector_sdk-2.3.0 → modulector_sdk-2.5.2}/src/modulector_sdk.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|