tooluniverse 0.2.0__py3-none-any.whl → 1.0.1__py3-none-any.whl

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.

Potentially problematic release.


This version of tooluniverse might be problematic. Click here for more details.

Files changed (186) hide show
  1. tooluniverse/__init__.py +340 -4
  2. tooluniverse/admetai_tool.py +84 -0
  3. tooluniverse/agentic_tool.py +563 -0
  4. tooluniverse/alphafold_tool.py +96 -0
  5. tooluniverse/base_tool.py +129 -6
  6. tooluniverse/boltz_tool.py +207 -0
  7. tooluniverse/chem_tool.py +192 -0
  8. tooluniverse/compose_scripts/__init__.py +1 -0
  9. tooluniverse/compose_scripts/biomarker_discovery.py +293 -0
  10. tooluniverse/compose_scripts/comprehensive_drug_discovery.py +186 -0
  11. tooluniverse/compose_scripts/drug_safety_analyzer.py +89 -0
  12. tooluniverse/compose_scripts/literature_tool.py +34 -0
  13. tooluniverse/compose_scripts/output_summarizer.py +279 -0
  14. tooluniverse/compose_scripts/tool_description_optimizer.py +681 -0
  15. tooluniverse/compose_scripts/tool_discover.py +705 -0
  16. tooluniverse/compose_scripts/tool_graph_composer.py +448 -0
  17. tooluniverse/compose_tool.py +371 -0
  18. tooluniverse/ctg_tool.py +1002 -0
  19. tooluniverse/custom_tool.py +81 -0
  20. tooluniverse/dailymed_tool.py +108 -0
  21. tooluniverse/data/admetai_tools.json +155 -0
  22. tooluniverse/data/adverse_event_tools.json +108 -0
  23. tooluniverse/data/agentic_tools.json +1156 -0
  24. tooluniverse/data/alphafold_tools.json +87 -0
  25. tooluniverse/data/boltz_tools.json +9 -0
  26. tooluniverse/data/chembl_tools.json +16 -0
  27. tooluniverse/data/clinicaltrials_gov_tools.json +326 -0
  28. tooluniverse/data/compose_tools.json +202 -0
  29. tooluniverse/data/dailymed_tools.json +70 -0
  30. tooluniverse/data/dataset_tools.json +646 -0
  31. tooluniverse/data/disease_target_score_tools.json +712 -0
  32. tooluniverse/data/efo_tools.json +17 -0
  33. tooluniverse/data/embedding_tools.json +319 -0
  34. tooluniverse/data/enrichr_tools.json +31 -0
  35. tooluniverse/data/europe_pmc_tools.json +22 -0
  36. tooluniverse/data/expert_feedback_tools.json +10 -0
  37. tooluniverse/data/fda_drug_adverse_event_tools.json +491 -0
  38. tooluniverse/data/fda_drug_labeling_tools.json +1 -1
  39. tooluniverse/data/fda_drugs_with_brand_generic_names_for_tool.py +76929 -148860
  40. tooluniverse/data/finder_tools.json +209 -0
  41. tooluniverse/data/gene_ontology_tools.json +113 -0
  42. tooluniverse/data/gwas_tools.json +1082 -0
  43. tooluniverse/data/hpa_tools.json +333 -0
  44. tooluniverse/data/humanbase_tools.json +47 -0
  45. tooluniverse/data/idmap_tools.json +74 -0
  46. tooluniverse/data/mcp_client_tools_example.json +113 -0
  47. tooluniverse/data/mcpautoloadertool_defaults.json +28 -0
  48. tooluniverse/data/medlineplus_tools.json +141 -0
  49. tooluniverse/data/monarch_tools.json +1 -1
  50. tooluniverse/data/openalex_tools.json +36 -0
  51. tooluniverse/data/opentarget_tools.json +1 -1
  52. tooluniverse/data/output_summarization_tools.json +101 -0
  53. tooluniverse/data/packages/bioinformatics_core_tools.json +1756 -0
  54. tooluniverse/data/packages/categorized_tools.txt +206 -0
  55. tooluniverse/data/packages/cheminformatics_tools.json +347 -0
  56. tooluniverse/data/packages/earth_sciences_tools.json +74 -0
  57. tooluniverse/data/packages/genomics_tools.json +776 -0
  58. tooluniverse/data/packages/image_processing_tools.json +38 -0
  59. tooluniverse/data/packages/machine_learning_tools.json +789 -0
  60. tooluniverse/data/packages/neuroscience_tools.json +62 -0
  61. tooluniverse/data/packages/original_tools.txt +0 -0
  62. tooluniverse/data/packages/physics_astronomy_tools.json +62 -0
  63. tooluniverse/data/packages/scientific_computing_tools.json +560 -0
  64. tooluniverse/data/packages/single_cell_tools.json +453 -0
  65. tooluniverse/data/packages/structural_biology_tools.json +396 -0
  66. tooluniverse/data/packages/visualization_tools.json +399 -0
  67. tooluniverse/data/pubchem_tools.json +215 -0
  68. tooluniverse/data/pubtator_tools.json +68 -0
  69. tooluniverse/data/rcsb_pdb_tools.json +1332 -0
  70. tooluniverse/data/reactome_tools.json +19 -0
  71. tooluniverse/data/semantic_scholar_tools.json +26 -0
  72. tooluniverse/data/special_tools.json +2 -25
  73. tooluniverse/data/tool_composition_tools.json +88 -0
  74. tooluniverse/data/toolfinderkeyword_defaults.json +34 -0
  75. tooluniverse/data/txagent_client_tools.json +9 -0
  76. tooluniverse/data/uniprot_tools.json +211 -0
  77. tooluniverse/data/url_fetch_tools.json +94 -0
  78. tooluniverse/data/uspto_downloader_tools.json +9 -0
  79. tooluniverse/data/uspto_tools.json +811 -0
  80. tooluniverse/data/xml_tools.json +3275 -0
  81. tooluniverse/dataset_tool.py +296 -0
  82. tooluniverse/default_config.py +165 -0
  83. tooluniverse/efo_tool.py +42 -0
  84. tooluniverse/embedding_database.py +630 -0
  85. tooluniverse/embedding_sync.py +396 -0
  86. tooluniverse/enrichr_tool.py +266 -0
  87. tooluniverse/europe_pmc_tool.py +52 -0
  88. tooluniverse/execute_function.py +1775 -95
  89. tooluniverse/extended_hooks.py +444 -0
  90. tooluniverse/gene_ontology_tool.py +194 -0
  91. tooluniverse/graphql_tool.py +158 -36
  92. tooluniverse/gwas_tool.py +358 -0
  93. tooluniverse/hpa_tool.py +1645 -0
  94. tooluniverse/humanbase_tool.py +389 -0
  95. tooluniverse/logging_config.py +254 -0
  96. tooluniverse/mcp_client_tool.py +764 -0
  97. tooluniverse/mcp_integration.py +413 -0
  98. tooluniverse/mcp_tool_registry.py +925 -0
  99. tooluniverse/medlineplus_tool.py +337 -0
  100. tooluniverse/openalex_tool.py +228 -0
  101. tooluniverse/openfda_adv_tool.py +283 -0
  102. tooluniverse/openfda_tool.py +393 -160
  103. tooluniverse/output_hook.py +1122 -0
  104. tooluniverse/package_tool.py +195 -0
  105. tooluniverse/pubchem_tool.py +158 -0
  106. tooluniverse/pubtator_tool.py +168 -0
  107. tooluniverse/rcsb_pdb_tool.py +38 -0
  108. tooluniverse/reactome_tool.py +108 -0
  109. tooluniverse/remote/boltz/boltz_mcp_server.py +50 -0
  110. tooluniverse/remote/depmap_24q2/depmap_24q2_mcp_tool.py +442 -0
  111. tooluniverse/remote/expert_feedback/human_expert_mcp_tools.py +2013 -0
  112. tooluniverse/remote/expert_feedback/simple_test.py +23 -0
  113. tooluniverse/remote/expert_feedback/start_web_interface.py +188 -0
  114. tooluniverse/remote/expert_feedback/web_only_interface.py +0 -0
  115. tooluniverse/remote/immune_compass/compass_tool.py +327 -0
  116. tooluniverse/remote/pinnacle/pinnacle_tool.py +328 -0
  117. tooluniverse/remote/transcriptformer/transcriptformer_tool.py +586 -0
  118. tooluniverse/remote/uspto_downloader/uspto_downloader_mcp_server.py +61 -0
  119. tooluniverse/remote/uspto_downloader/uspto_downloader_tool.py +120 -0
  120. tooluniverse/remote_tool.py +99 -0
  121. tooluniverse/restful_tool.py +53 -30
  122. tooluniverse/scripts/generate_tool_graph.py +408 -0
  123. tooluniverse/scripts/visualize_tool_graph.py +829 -0
  124. tooluniverse/semantic_scholar_tool.py +62 -0
  125. tooluniverse/smcp.py +2452 -0
  126. tooluniverse/smcp_server.py +975 -0
  127. tooluniverse/test/mcp_server_test.py +0 -0
  128. tooluniverse/test/test_admetai_tool.py +370 -0
  129. tooluniverse/test/test_agentic_tool.py +129 -0
  130. tooluniverse/test/test_alphafold_tool.py +71 -0
  131. tooluniverse/test/test_chem_tool.py +37 -0
  132. tooluniverse/test/test_compose_lieraturereview.py +63 -0
  133. tooluniverse/test/test_compose_tool.py +448 -0
  134. tooluniverse/test/test_dailymed.py +69 -0
  135. tooluniverse/test/test_dataset_tool.py +200 -0
  136. tooluniverse/test/test_disease_target_score.py +56 -0
  137. tooluniverse/test/test_drugbank_filter_examples.py +179 -0
  138. tooluniverse/test/test_efo.py +31 -0
  139. tooluniverse/test/test_enrichr_tool.py +21 -0
  140. tooluniverse/test/test_europe_pmc_tool.py +20 -0
  141. tooluniverse/test/test_fda_adv.py +95 -0
  142. tooluniverse/test/test_fda_drug_labeling.py +91 -0
  143. tooluniverse/test/test_gene_ontology_tools.py +66 -0
  144. tooluniverse/test/test_gwas_tool.py +139 -0
  145. tooluniverse/test/test_hpa.py +625 -0
  146. tooluniverse/test/test_humanbase_tool.py +20 -0
  147. tooluniverse/test/test_idmap_tools.py +61 -0
  148. tooluniverse/test/test_mcp_server.py +211 -0
  149. tooluniverse/test/test_mcp_tool.py +247 -0
  150. tooluniverse/test/test_medlineplus.py +220 -0
  151. tooluniverse/test/test_openalex_tool.py +32 -0
  152. tooluniverse/test/test_opentargets.py +28 -0
  153. tooluniverse/test/test_pubchem_tool.py +116 -0
  154. tooluniverse/test/test_pubtator_tool.py +37 -0
  155. tooluniverse/test/test_rcsb_pdb_tool.py +86 -0
  156. tooluniverse/test/test_reactome.py +54 -0
  157. tooluniverse/test/test_semantic_scholar_tool.py +24 -0
  158. tooluniverse/test/test_software_tools.py +147 -0
  159. tooluniverse/test/test_tool_description_optimizer.py +49 -0
  160. tooluniverse/test/test_tool_finder.py +26 -0
  161. tooluniverse/test/test_tool_finder_llm.py +252 -0
  162. tooluniverse/test/test_tools_find.py +195 -0
  163. tooluniverse/test/test_uniprot_tools.py +74 -0
  164. tooluniverse/test/test_uspto_tool.py +72 -0
  165. tooluniverse/test/test_xml_tool.py +113 -0
  166. tooluniverse/tool_finder_embedding.py +267 -0
  167. tooluniverse/tool_finder_keyword.py +693 -0
  168. tooluniverse/tool_finder_llm.py +699 -0
  169. tooluniverse/tool_graph_web_ui.py +955 -0
  170. tooluniverse/tool_registry.py +416 -0
  171. tooluniverse/uniprot_tool.py +155 -0
  172. tooluniverse/url_tool.py +253 -0
  173. tooluniverse/uspto_tool.py +240 -0
  174. tooluniverse/utils.py +369 -41
  175. tooluniverse/xml_tool.py +369 -0
  176. tooluniverse-1.0.1.dist-info/METADATA +387 -0
  177. tooluniverse-1.0.1.dist-info/RECORD +182 -0
  178. tooluniverse-1.0.1.dist-info/entry_points.txt +9 -0
  179. tooluniverse/generate_mcp_tools.py +0 -113
  180. tooluniverse/mcp_server.py +0 -3340
  181. tooluniverse-0.2.0.dist-info/METADATA +0 -139
  182. tooluniverse-0.2.0.dist-info/RECORD +0 -21
  183. tooluniverse-0.2.0.dist-info/entry_points.txt +0 -4
  184. {tooluniverse-0.2.0.dist-info → tooluniverse-1.0.1.dist-info}/WHEEL +0 -0
  185. {tooluniverse-0.2.0.dist-info → tooluniverse-1.0.1.dist-info}/licenses/LICENSE +0 -0
  186. {tooluniverse-0.2.0.dist-info → tooluniverse-1.0.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,23 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Simple Test for Human Expert System
4
+ """
5
+
6
+ from tooluniverse import ToolUniverse
7
+
8
+ tooluni = ToolUniverse()
9
+ tooluni.load_tools()
10
+
11
+ # Submit question to expert
12
+ result = tooluni.run(
13
+ {
14
+ "name": "expert_consult_human_expert",
15
+ "arguments": {
16
+ "question": "What is the recommended dosage of aspirin for elderly patients?",
17
+ "specialty": "cardiology",
18
+ "priority": "high", # normal, high, urgent
19
+ },
20
+ }
21
+ )
22
+ print("Request submitted. Result:")
23
+ print(result)
@@ -0,0 +1,188 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Start Web Interface Only
4
+
5
+ This script starts only the web interface for expert consultations using the
6
+ @register_mcp_tool system (human_expert_mcp_tools.py).
7
+ The MCP server must be running separately.
8
+ """
9
+
10
+ import subprocess
11
+ import sys
12
+ import requests
13
+ from pathlib import Path
14
+ import os
15
+ import socket
16
+
17
+
18
+ def get_random_port():
19
+ """Get a random available port"""
20
+ with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
21
+ s.bind(("", 0))
22
+ return s.getsockname()[1]
23
+
24
+
25
+ def detect_mcp_server_port():
26
+ """Ask user for MCP server connection details"""
27
+ print("📝 Please provide the MCP server connection details:")
28
+
29
+ while True:
30
+ try:
31
+ address = input("Enter server IP address (default: localhost): ").strip()
32
+ if not address:
33
+ address = "localhost"
34
+
35
+ port_input = input("Enter server port (default: 9877): ").strip()
36
+ if not port_input:
37
+ port = 9876
38
+ else:
39
+ port = int(port_input)
40
+
41
+ # Validate port range
42
+ if not (1 <= port <= 65535):
43
+ print("❌ Port must be between 1 and 65535. Please try again.")
44
+ continue
45
+
46
+ # Test the connection
47
+ print(f"🔍 Testing connection to {address}:{port}...")
48
+ if check_mcp_server_on_port(port, address):
49
+ print(f"✅ Successfully connected to {address}:{port}")
50
+ # Set the environment variable for this session
51
+ os.environ["EXPERT_FEEDBACK_MCP_SERVER_URL"] = (
52
+ f"http://{address}:{port}"
53
+ )
54
+ return port
55
+ else:
56
+ print(f"❌ Could not connect to {address}:{port}")
57
+ print("💡 Make sure the MCP server is running and accessible")
58
+ print(" Server logs should show something like:")
59
+ print(" 'INFO: Uvicorn running on http://0.0.0.0:9876'")
60
+ retry = input("Would you like to try again? (y/N): ").strip().lower()
61
+ if retry != "y":
62
+ break
63
+
64
+ except ValueError:
65
+ print("❌ Invalid port number. Please enter a valid integer.")
66
+ except KeyboardInterrupt:
67
+ print("\n👋 Cancelled by user")
68
+ return None
69
+
70
+ # Try common ports that the system might use
71
+ print("🔍 Trying common ports...")
72
+ common_ports = [8000, 8001] + [52340 + i for i in range(20)]
73
+
74
+ for port in common_ports:
75
+ if check_mcp_server_on_port(port):
76
+ print(f"✅ Found MCP server running on port {port}")
77
+ return port
78
+
79
+ return None
80
+
81
+
82
+ def check_mcp_server_on_port(port, address="localhost"):
83
+ """Check if MCP server is running on specific port and address"""
84
+ try:
85
+ # Try different possible endpoints
86
+ endpoints_to_try = [
87
+ f"http://{address}:{port}/tools/get_expert_status",
88
+ f"http://{address}:{port}/mcp",
89
+ f"http://{address}:{port}/tools",
90
+ f"http://{address}:{port}/",
91
+ f"http://{address}:{port}/docs",
92
+ f"http://{address}:{port}/health",
93
+ ]
94
+
95
+ for endpoint in endpoints_to_try:
96
+ try:
97
+ if "/tools/get_expert_status" in endpoint:
98
+ response = requests.post(endpoint, json={}, timeout=2)
99
+ else:
100
+ response = requests.get(endpoint, timeout=2)
101
+
102
+ # Accept various response codes that indicate server is running
103
+ if response.status_code in [200, 404, 405, 406, 422, 500]:
104
+ print(
105
+ f"✅ Server detected at {endpoint} (status: {response.status_code})"
106
+ )
107
+ return True
108
+
109
+ except requests.exceptions.RequestException:
110
+ continue
111
+
112
+ except Exception as e:
113
+ print(f"⚠️ Connection test error: {e}")
114
+
115
+ return False
116
+
117
+
118
+ def check_mcp_server():
119
+ """Check if MCP server is running and return port"""
120
+ print("🔍 Detecting MCP server...")
121
+ port = detect_mcp_server_port()
122
+ if port:
123
+ print(f"✅ MCP Server detected on port {port}")
124
+ return port
125
+ return None
126
+
127
+
128
+ def main():
129
+ print("🌐 Starting Human Expert Web Interface")
130
+ print("=" * 50)
131
+
132
+ # Check if MCP server is running
133
+ mcp_port = check_mcp_server()
134
+ if not mcp_port:
135
+ print("❌ Second generation MCP Server not detected!")
136
+ print("📡 Please start the MCP server first:")
137
+ print(" python human_expert_mcp_tools.py --start-server")
138
+ print()
139
+ print("💡 This script only supports the modern @register_mcp_tool system.")
140
+ print()
141
+ choice = input("Continue anyway? (y/N): ").strip().lower()
142
+ if choice != "y":
143
+ return 1
144
+
145
+ # Check for the second generation script
146
+ modern_script = Path(__file__).parent / "human_expert_mcp_tools.py"
147
+
148
+ if not modern_script.exists():
149
+ print("❌ MCP tools script not found!")
150
+ print(f" Looking for: {modern_script}")
151
+ print(" Make sure human_expert_mcp_tools.py exists in this directory.")
152
+ return 1
153
+
154
+ print("🚀 Using @register_mcp_tool system")
155
+ if mcp_port:
156
+ print(f"🔌 MCP Server: localhost:{mcp_port}")
157
+
158
+ print("🌐 Starting web interface...")
159
+ print("🖥️ Browser should open automatically")
160
+ print("\nPress Ctrl+C to stop")
161
+ print("=" * 60)
162
+
163
+ try:
164
+ # Check Flask availability
165
+ try:
166
+ pass
167
+
168
+ print("✅ Flask is available")
169
+ except ImportError:
170
+ print("❌ Flask not found. Install with: pip install flask")
171
+ return 1
172
+
173
+ # Start web interface
174
+ subprocess.run(
175
+ [sys.executable, str(modern_script), "--web-only", "--no-browser"]
176
+ )
177
+ return 0
178
+
179
+ except KeyboardInterrupt:
180
+ print("\n👋 Web Interface stopped")
181
+ return 0
182
+ except Exception as e:
183
+ print(f"❌ Error starting web interface: {str(e)}")
184
+ return 1
185
+
186
+
187
+ if __name__ == "__main__":
188
+ sys.exit(main())
@@ -0,0 +1,327 @@
1
+ """
2
+ COMPASS Prediction Tool - MCP Server
3
+
4
+ This module provides an MCP (Model Context Protocol) server for running immune checkpoint
5
+ inhibitor (ICI) response predictions using the COMPASS (COMprehensive Pathway Analysis
6
+ for Single-cell Sequencing) model. The tool processes tumor gene expression data to
7
+ predict patient responsiveness to immunotherapy.
8
+
9
+ The COMPASS model analyzes gene expression profiles to identify key immune cell
10
+ populations and pathways that contribute to treatment response prediction.
11
+ """
12
+
13
+ import os
14
+ import sys
15
+ import torch
16
+ import pandas as pd
17
+ import asyncio
18
+ import uuid
19
+ from fastmcp import FastMCP
20
+ from typing import List, Tuple, Optional
21
+
22
+ sys.path.insert(
23
+ 0, f'{os.getenv("COMPASS_MODEL_PATH")}/immune-compass/COMPASS'
24
+ ) # noqa: E402
25
+
26
+ from compass import loadcompass # noqa: E402
27
+
28
+ # Initialize MCP Server for COMPASS predictions
29
+ server = FastMCP("COMPASS Prediction SMCP Server")
30
+
31
+
32
+ class CompassTool:
33
+ """
34
+ A comprehensive tool for running immune checkpoint inhibitor (ICI) response predictions
35
+ using the COMPASS model.
36
+
37
+ This class provides functionality to:
38
+ - Load pre-trained COMPASS model checkpoints
39
+ - Process gene expression data (TPM format)
40
+ - Predict ICI treatment response
41
+ - Extract key immune cell populations contributing to predictions
42
+
43
+ The COMPASS model is trained to identify immune cell concepts and pathways
44
+ that are predictive of patient response to checkpoint inhibitor therapy.
45
+ """
46
+
47
+ def __init__(
48
+ self,
49
+ root_path: Optional[str] = None,
50
+ ckp_path: str = "pft_leave_IMVigor210.pt",
51
+ device: str = "cpu",
52
+ ):
53
+ """
54
+ Initializes the COMPASS tool by loading the pre-trained model checkpoint.
55
+
56
+ Args:
57
+ root_path (str, optional): Path to the directory containing model checkpoints.
58
+ If None, uses COMPASS_MODEL_PATH/immune-compass/checkpoint.
59
+ ckp_path (str): Name of the checkpoint file to load.
60
+ Defaults to "pft_leave_IMVigor210.pt" (IMVigor210 cohort).
61
+ device (str): Device for model inference ("cuda" or "cpu"). Defaults to "cuda".
62
+
63
+ Raises:
64
+ FileNotFoundError: If the specified checkpoint file cannot be found.
65
+ Exception: If model loading fails due to compatibility or corruption issues.
66
+ """
67
+ # Construct model checkpoint path
68
+ if root_path is None:
69
+ compass_model_path = os.getenv("COMPASS_MODEL_PATH")
70
+ if compass_model_path is None:
71
+ raise ValueError("COMPASS_MODEL_PATH environment variable is not set")
72
+ if not os.path.exists(
73
+ os.path.join(compass_model_path, "immune-compass", "checkpoint")
74
+ ):
75
+ checkpoint_path = os.path.join(
76
+ compass_model_path, "immune-compass", "checkpoint"
77
+ )
78
+ raise FileNotFoundError(
79
+ f"COMPASS model checkpoint not found at {checkpoint_path}. Please check your COMPASS_MODEL_PATH."
80
+ )
81
+ root_path = os.path.join(compass_model_path, "immune-compass", "checkpoint")
82
+
83
+ self.model_path = os.path.join(root_path, ckp_path)
84
+ self.device = device
85
+
86
+ # Load the pre-trained COMPASS model
87
+ print(f"🛠️ Initializing COMPASS tool from checkpoint: {self.model_path}...")
88
+ self.finetuner = loadcompass(
89
+ self.model_path, weights_only=False, map_location=torch.device(self.device)
90
+ )
91
+
92
+ # Configure device settings for CPU inference if needed
93
+ if self.device == "cpu":
94
+ self.finetuner.device = "cpu"
95
+
96
+ # Display model parameter count for transparency
97
+ self.finetuner.count_parameters()
98
+ print(
99
+ "[COMPASS] Tool initialized successfully (model loaded and ready for predictions)."
100
+ )
101
+
102
+ def _get_top_columns_per_row(
103
+ self,
104
+ df: pd.DataFrame,
105
+ top_n: int = 44,
106
+ exclude: Optional[List[str]] = None,
107
+ ) -> List[List[Tuple[str, float]]]:
108
+ """
109
+ Extracts the top-scoring immune cell concepts for each sample from COMPASS output.
110
+
111
+ This method processes the COMPASS cell concept matrix to identify the most
112
+ influential immune cell populations contributing to the prediction for each sample.
113
+
114
+ Args:
115
+ df (pd.DataFrame): DataFrame containing cell concept scores from COMPASS analysis.
116
+ Rows represent samples, columns represent immune cell concepts.
117
+ top_n (int): Maximum number of top concepts to return per sample. Defaults to 44.
118
+ exclude (List[str]): List of column names to exclude from results.
119
+ Defaults to ['CANCER', 'Reference'].
120
+
121
+ Returns:
122
+ List[List[Tuple[str, float]]]: For each sample, a list of tuples containing
123
+ (concept_name, concept_score) sorted by score descending.
124
+ """
125
+ # Set default excludes safely to avoid mutable default argument
126
+ if exclude is None:
127
+ exclude = ["CANCER", "Reference"]
128
+ # Sort concepts by score (descending) for each sample
129
+ sorted_concepts_indices = [
130
+ row.sort_values(ascending=False).index[:top_n] for _, row in df.iterrows()
131
+ ]
132
+
133
+ results = []
134
+ for i, (_, row) in enumerate(df.iterrows()):
135
+ row_concepts = []
136
+ for col in sorted_concepts_indices[i]:
137
+ # Skip excluded columns (e.g., metadata columns)
138
+ if col not in exclude:
139
+ row_concepts.append((col, row[col]))
140
+ results.append(row_concepts)
141
+ return results
142
+
143
+ def predict(
144
+ self,
145
+ gene_expression_data_path: str,
146
+ threshold: float = 0.5,
147
+ batch_size: int = 128,
148
+ ) -> Tuple[bool, List[Tuple[str, float]]]:
149
+ """
150
+ Performs immune checkpoint inhibitor response prediction on gene expression data.
151
+
152
+ This method processes single-sample tumor gene expression data (in TPM format)
153
+ through the COMPASS model to predict treatment response and identify key
154
+ immune cell populations contributing to the prediction.
155
+
156
+ Args:
157
+ gene_expression_data_path (str): Path to the TPM expression data file.
158
+ to their expression levels in Transcripts Per Million (TPM).
159
+ threshold (float): Prediction probability threshold for classifying samples as responders.
160
+ Values ≥ threshold are classified as responders. Defaults to 0.5.
161
+ batch_size (int): Batch size for model inference. Larger values may improve speed
162
+ but require more memory. Defaults to 128.
163
+
164
+ Returns:
165
+ Tuple[bool, List[Tuple[str, float]]]: A tuple containing:
166
+ - bool: True if predicted as responder (probability ≥ threshold), False otherwise
167
+ - List[Tuple[str, float]]: Top immune cell concepts ranked by importance,
168
+ where each tuple contains (concept_name, concept_score)
169
+
170
+ Raises:
171
+ ValueError: If gene_expression_data is empty or contains invalid values.
172
+ RuntimeError: If model inference fails.
173
+ """
174
+ # Convert gene expression dictionary to DataFrame format expected by COMPASS
175
+ df_tpm = pd.read_pickle(gene_expression_data_path)
176
+ df_tpm.index.name = "Index" # Required by COMPASS for gene indexing
177
+
178
+ # Extract immune cell concepts and generate predictions using COMPASS model
179
+ # dfct contains cell concept scores, dfpred contains response probabilities
180
+ _, _, dfct = self.finetuner.extract(
181
+ df_tpm, batch_size=batch_size, with_gene_level=True
182
+ )
183
+ _, dfpred = self.finetuner.predict(df_tpm)
184
+
185
+ # Extract and rank the most influential immune cell concepts
186
+ sorted_cell_concepts = self._get_top_columns_per_row(dfct)
187
+
188
+ # Classify sample as responder based on prediction probability threshold
189
+ # dfpred.iloc[:, 1] contains the responder probability (column 1)
190
+ responder = dfpred.iloc[:, 1].max() >= threshold
191
+
192
+ return responder, sorted_cell_concepts[0] if sorted_cell_concepts else []
193
+
194
+
195
+ @server.tool()
196
+ async def run_compass_prediction(
197
+ gene_expression_data_path: str,
198
+ threshold: float = 0.5,
199
+ root_path: Optional[str] = None,
200
+ ):
201
+ """
202
+ MCP Tool: Predicts immune checkpoint inhibitor (ICI) response using COMPASS model.
203
+
204
+ This tool analyzes single-sample tumor gene expression data to predict patient
205
+ responsiveness to immune checkpoint inhibitor therapy. The COMPASS model leverages
206
+ immune cell concept analysis to provide both a binary prediction and interpretable
207
+ insights into the immune microenvironment factors driving the prediction.
208
+
209
+ Clinical Context:
210
+ - Designed for precision oncology applications
211
+ - Helps identify patients likely to benefit from ICI therapy
212
+ - Provides mechanistic insights through immune cell population analysis
213
+ - Based on validated cohorts including IMVigor210 (urothelial carcinoma)
214
+
215
+ Args:
216
+ gene_expression_data_path (str): Path to the TPM expression data file.
217
+ Keys should be standard gene symbols (e.g., "CD274", "PDCD1", "CTLA4")
218
+ Values should be normalized expression in TPM (Transcripts Per Million).
219
+ Minimum ~100 genes recommended for reliable predictions.
220
+ threshold (float): Probability threshold for responder classification (0.0-1.0).
221
+ Values ≥ threshold classify sample as likely responder.
222
+ Default 0.5 provides balanced sensitivity/specificity.
223
+ Consider lower thresholds (~0.3) for higher sensitivity.
224
+
225
+ Returns:
226
+ dict: Structured prediction results containing:
227
+ - 'prediction' (dict): Core prediction results with:
228
+ * 'is_responder' (bool): True if predicted responder (probability ≥ threshold)
229
+ * 'top_concepts' (list): Ranked immune cell concepts as dicts with:
230
+ - 'concept' (str): Name of immune cell population/concept
231
+ - 'score' (float): Importance score for this concept
232
+ - 'context_info' (list): Human-readable analysis summary and status messages
233
+ - 'error' (str, optional): Error description if prediction failed
234
+ """
235
+ # Generate unique request ID for tracking and logging
236
+ request_id = str(uuid.uuid4())[:8]
237
+ print(f"[{request_id}] Received COMPASS ICI response prediction request")
238
+
239
+ # Initialize global COMPASS tool instance for MCP server
240
+ # This instance will be used by the MCP tool function to serve predictions
241
+ try:
242
+ compass_tool = CompassTool(root_path=root_path)
243
+ print("✅ COMPASS Prediction tool instance created and ready for MCP server")
244
+ except Exception as e:
245
+ print(f"❌ Error creating COMPASS Prediction tool: {str(e)}")
246
+ print(
247
+ "Please ensure COMPASS_MODEL_PATH is correctly set and model checkpoint exists."
248
+ )
249
+ raise e
250
+
251
+ try:
252
+ # Brief async pause to allow for proper request handling
253
+ await asyncio.sleep(0.1)
254
+
255
+ # Validate input parameters
256
+ if (
257
+ not isinstance(gene_expression_data_path, str)
258
+ or not gene_expression_data_path
259
+ ):
260
+ raise ValueError(
261
+ "Input 'gene_expression_data' must be a non-empty dictionary mapping gene symbols to TPM values."
262
+ )
263
+
264
+ if not (0.0 <= threshold <= 1.0):
265
+ raise ValueError(f"Threshold must be between 0.0 and 1.0, got {threshold}")
266
+
267
+ print(
268
+ f"[{request_id}] Processing {len(gene_expression_data_path)} genes with threshold {threshold}"
269
+ )
270
+
271
+ # Execute COMPASS model prediction
272
+ is_responder, top_concepts = compass_tool.predict(
273
+ gene_expression_data_path, threshold=threshold
274
+ )
275
+
276
+ # Convert concept tuples to JSON-serializable format
277
+ serializable_concepts = [
278
+ {"concept": concept, "score": float(score)}
279
+ for concept, score in top_concepts
280
+ ]
281
+
282
+ # Log successful completion
283
+ response_status = "RESPONDER" if is_responder else "NON-RESPONDER"
284
+ print(
285
+ f"[{request_id}] ✅ COMPASS prediction completed: {response_status} ({len(serializable_concepts)} concepts)"
286
+ )
287
+
288
+ return {
289
+ "prediction": {
290
+ "is_responder": is_responder,
291
+ "top_concepts": serializable_concepts,
292
+ },
293
+ "context_info": [
294
+ "COMPASS prediction completed successfully.",
295
+ f"Sample classified as: {'RESPONDER' if is_responder else 'NON-RESPONDER'}",
296
+ f"Analysis based on {len(gene_expression_data_path)} input genes.",
297
+ f"Top {len(serializable_concepts)} immune cell concepts identified.",
298
+ ],
299
+ }
300
+
301
+ except (ValueError, FileNotFoundError) as e:
302
+ error_message = f"COMPASS prediction validation error: {str(e)}"
303
+ print(f"[{request_id}] {error_message}")
304
+ return {
305
+ "error": error_message,
306
+ "context_info": ["Please check input data format and model availability."],
307
+ }
308
+ except Exception as e:
309
+ error_message = f"Unexpected error during COMPASS prediction: {str(e)}"
310
+ print(f"[{request_id}] {error_message}")
311
+ return {
312
+ "error": error_message,
313
+ "context_info": ["Internal server error occurred during prediction."],
314
+ }
315
+
316
+
317
+ if __name__ == "__main__":
318
+ print("Starting MCP server for COMPASS Immune Response Prediction Tool...")
319
+ print("Model: COMPASS (COMprehensive Pathway Analysis for Single-cell Sequencing)")
320
+ print("Application: Immune Checkpoint Inhibitor Response Prediction")
321
+ print("Server: FastMCP with streamable HTTP transport")
322
+ print("Port: 7003 (configured to avoid conflicts with other biomedical tools)")
323
+
324
+ # Launch the MCP server with COMPASS prediction capabilities
325
+ server.run(
326
+ transport="streamable-http", host="0.0.0.0", port=7003, stateless_http=True
327
+ )