modulector-sdk 2.4.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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modulector-sdk
3
- Version: 2.4.0
3
+ Version: 2.5.2
4
4
  Summary: Typed Python SDK for the Modulector API.
5
5
  Author: omicsdatascience
6
6
  License-Expression: MIT
@@ -101,6 +101,9 @@ disease associations, drug associations, and PubMed news subscriptions. Use
101
101
  `MODULECTOR_API_BASE_URL` or each tool's `base_url` argument to target a custom
102
102
  Modulector deployment.
103
103
 
104
+ When deployed with the root Docker Compose stack, nginx proxies the MCP
105
+ Streamable HTTP endpoint at `https://<mydomain.com>/mcp`.
106
+
104
107
  ## Development
105
108
 
106
109
  Build this SDK from the `sdk/` directory:
@@ -70,6 +70,9 @@ disease associations, drug associations, and PubMed news subscriptions. Use
70
70
  `MODULECTOR_API_BASE_URL` or each tool's `base_url` argument to target a custom
71
71
  Modulector deployment.
72
72
 
73
+ When deployed with the root Docker Compose stack, nginx proxies the MCP
74
+ Streamable HTTP endpoint at `https://<mydomain.com>/mcp`.
75
+
73
76
  ## Development
74
77
 
75
78
  Build this SDK from the `sdk/` directory:
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "modulector-sdk"
7
- version = "2.4.0"
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"
@@ -3,9 +3,12 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  import argparse
6
+ import os
6
7
  from typing import Any, Final, Literal, TypeVar, cast
8
+ from urllib.parse import urlparse
7
9
 
8
10
  from mcp.server.fastmcp import FastMCP
11
+ from mcp.server.transport_security import TransportSecuritySettings
9
12
 
10
13
  from . import services
11
14
  from .utils import PaginatedResponse
@@ -21,11 +24,15 @@ SERVER_INSTRUCTIONS: Final[str] = (
21
24
  "paginated tools with explicit identifiers. The returned data comes from "
22
25
  "the configured Modulector API deployment."
23
26
  )
27
+ PUBLIC_BASE_URL: Final[str] = os.getenv(
28
+ "MODULECTOR_PUBLIC_BASE_URL",
29
+ services.MODULECTOR_API_BASE_URL,
30
+ )
24
31
 
25
32
  mcp = FastMCP(
26
33
  name="modulector",
27
34
  instructions=SERVER_INSTRUCTIONS,
28
- website_url=services.MODULECTOR_API_BASE_URL,
35
+ website_url=PUBLIC_BASE_URL,
29
36
  )
30
37
 
31
38
 
@@ -48,6 +55,7 @@ def about_modulector() -> str:
48
55
  "miRNA-drug associations, and Illumina Infinium MethylationEPIC 2.0 "
49
56
  "site annotations.\n\n"
50
57
  f"Default API base URL: `{services.MODULECTOR_API_BASE_URL}`\n\n"
58
+ f"Public website URL: `{PUBLIC_BASE_URL}`\n\n"
51
59
  "Use the finder tools for partial identifiers, then query detail or "
52
60
  "paginated tools with exact miRNA, gene, disease, drug, or methylation "
53
61
  "site filters."
@@ -135,10 +143,13 @@ def get_mirna_details(
135
143
  :return: miRNA detail record.
136
144
  """
137
145
 
138
- return services.get_mirna_details(
139
- mirna,
140
- base_url=_base_url(base_url),
141
- timeout=timeout,
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
+ ),
142
153
  )
143
154
 
144
155
 
@@ -308,10 +319,13 @@ def get_methylation_details(
308
319
  :return: Methylation site detail record.
309
320
  """
310
321
 
311
- return services.get_methylation_details(
312
- methylation_site,
313
- base_url=_base_url(base_url),
314
- timeout=timeout,
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
+ ),
315
329
  )
316
330
 
317
331
 
@@ -400,6 +414,7 @@ def main() -> None:
400
414
  mcp.settings.host = args.host
401
415
  mcp.settings.port = args.port
402
416
  mcp.settings.json_response = args.json_response
417
+ _configure_http_transport_security()
403
418
  mcp.run(transport=transport)
404
419
 
405
420
 
@@ -447,6 +462,63 @@ def _base_url(base_url: str | None) -> str:
447
462
  return base_url or services.MODULECTOR_API_BASE_URL
448
463
 
449
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
+
450
522
  def _page_to_dict(page: PaginatedResponse[T]) -> dict[str, Any]:
451
523
  """Convert a paginated SDK response into a JSON-serializable mapping.
452
524
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modulector-sdk
3
- Version: 2.4.0
3
+ Version: 2.5.2
4
4
  Summary: Typed Python SDK for the Modulector API.
5
5
  Author: omicsdatascience
6
6
  License-Expression: MIT
@@ -101,6 +101,9 @@ disease associations, drug associations, and PubMed news subscriptions. Use
101
101
  `MODULECTOR_API_BASE_URL` or each tool's `base_url` argument to target a custom
102
102
  Modulector deployment.
103
103
 
104
+ When deployed with the root Docker Compose stack, nginx proxies the MCP
105
+ Streamable HTTP endpoint at `https://<mydomain.com>/mcp`.
106
+
104
107
  ## Development
105
108
 
106
109
  Build this SDK from the `sdk/` directory:
File without changes
File without changes