tooluniverse 0.2.0__py3-none-any.whl → 1.0.0__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 (190) 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/agentic_tools.json +1156 -0
  23. tooluniverse/data/alphafold_tools.json +87 -0
  24. tooluniverse/data/boltz_tools.json +9 -0
  25. tooluniverse/data/chembl_tools.json +16 -0
  26. tooluniverse/data/clait_tools.json +108 -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/software_tools.json +4954 -0
  66. tooluniverse/data/packages/structural_biology_tools.json +396 -0
  67. tooluniverse/data/packages/visualization_tools.json +399 -0
  68. tooluniverse/data/pubchem_tools.json +215 -0
  69. tooluniverse/data/pubtator_tools.json +68 -0
  70. tooluniverse/data/rcsb_pdb_tools.json +1332 -0
  71. tooluniverse/data/reactome_tools.json +19 -0
  72. tooluniverse/data/semantic_scholar_tools.json +26 -0
  73. tooluniverse/data/special_tools.json +2 -25
  74. tooluniverse/data/tool_composition_tools.json +88 -0
  75. tooluniverse/data/toolfinderkeyword_defaults.json +34 -0
  76. tooluniverse/data/txagent_client_tools.json +9 -0
  77. tooluniverse/data/uniprot_tools.json +211 -0
  78. tooluniverse/data/url_fetch_tools.json +94 -0
  79. tooluniverse/data/uspto_downloader_tools.json +9 -0
  80. tooluniverse/data/uspto_tools.json +811 -0
  81. tooluniverse/data/xml_tools.json +3275 -0
  82. tooluniverse/dataset_tool.py +296 -0
  83. tooluniverse/default_config.py +165 -0
  84. tooluniverse/efo_tool.py +42 -0
  85. tooluniverse/embedding_database.py +630 -0
  86. tooluniverse/embedding_sync.py +396 -0
  87. tooluniverse/enrichr_tool.py +266 -0
  88. tooluniverse/europe_pmc_tool.py +52 -0
  89. tooluniverse/execute_function.py +1775 -95
  90. tooluniverse/extended_hooks.py +444 -0
  91. tooluniverse/gene_ontology_tool.py +194 -0
  92. tooluniverse/graphql_tool.py +158 -36
  93. tooluniverse/gwas_tool.py +358 -0
  94. tooluniverse/hpa_tool.py +1645 -0
  95. tooluniverse/humanbase_tool.py +389 -0
  96. tooluniverse/logging_config.py +254 -0
  97. tooluniverse/mcp_client_tool.py +764 -0
  98. tooluniverse/mcp_integration.py +413 -0
  99. tooluniverse/mcp_tool_registry.py +925 -0
  100. tooluniverse/medlineplus_tool.py +337 -0
  101. tooluniverse/openalex_tool.py +228 -0
  102. tooluniverse/openfda_adv_tool.py +283 -0
  103. tooluniverse/openfda_tool.py +393 -160
  104. tooluniverse/output_hook.py +1122 -0
  105. tooluniverse/package_tool.py +195 -0
  106. tooluniverse/pubchem_tool.py +158 -0
  107. tooluniverse/pubtator_tool.py +168 -0
  108. tooluniverse/rcsb_pdb_tool.py +38 -0
  109. tooluniverse/reactome_tool.py +108 -0
  110. tooluniverse/remote/boltz/boltz_mcp_server.py +50 -0
  111. tooluniverse/remote/depmap_24q2/depmap_24q2_mcp_tool.py +442 -0
  112. tooluniverse/remote/expert_feedback/human_expert_mcp_tools.py +2013 -0
  113. tooluniverse/remote/expert_feedback/simple_test.py +23 -0
  114. tooluniverse/remote/expert_feedback/start_web_interface.py +188 -0
  115. tooluniverse/remote/expert_feedback/web_only_interface.py +0 -0
  116. tooluniverse/remote/expert_feedback_mcp/human_expert_mcp_server.py +1611 -0
  117. tooluniverse/remote/expert_feedback_mcp/simple_test.py +34 -0
  118. tooluniverse/remote/expert_feedback_mcp/start_web_interface.py +91 -0
  119. tooluniverse/remote/immune_compass/compass_tool.py +327 -0
  120. tooluniverse/remote/pinnacle/pinnacle_tool.py +328 -0
  121. tooluniverse/remote/transcriptformer/transcriptformer_tool.py +586 -0
  122. tooluniverse/remote/uspto_downloader/uspto_downloader_mcp_server.py +61 -0
  123. tooluniverse/remote/uspto_downloader/uspto_downloader_tool.py +120 -0
  124. tooluniverse/remote_tool.py +99 -0
  125. tooluniverse/restful_tool.py +53 -30
  126. tooluniverse/scripts/generate_tool_graph.py +408 -0
  127. tooluniverse/scripts/visualize_tool_graph.py +829 -0
  128. tooluniverse/semantic_scholar_tool.py +62 -0
  129. tooluniverse/smcp.py +2452 -0
  130. tooluniverse/smcp_server.py +975 -0
  131. tooluniverse/test/mcp_server_test.py +0 -0
  132. tooluniverse/test/test_admetai_tool.py +370 -0
  133. tooluniverse/test/test_agentic_tool.py +129 -0
  134. tooluniverse/test/test_alphafold_tool.py +71 -0
  135. tooluniverse/test/test_chem_tool.py +37 -0
  136. tooluniverse/test/test_compose_lieraturereview.py +63 -0
  137. tooluniverse/test/test_compose_tool.py +448 -0
  138. tooluniverse/test/test_dailymed.py +69 -0
  139. tooluniverse/test/test_dataset_tool.py +200 -0
  140. tooluniverse/test/test_disease_target_score.py +56 -0
  141. tooluniverse/test/test_drugbank_filter_examples.py +179 -0
  142. tooluniverse/test/test_efo.py +31 -0
  143. tooluniverse/test/test_enrichr_tool.py +21 -0
  144. tooluniverse/test/test_europe_pmc_tool.py +20 -0
  145. tooluniverse/test/test_fda_adv.py +95 -0
  146. tooluniverse/test/test_fda_drug_labeling.py +91 -0
  147. tooluniverse/test/test_gene_ontology_tools.py +66 -0
  148. tooluniverse/test/test_gwas_tool.py +139 -0
  149. tooluniverse/test/test_hpa.py +625 -0
  150. tooluniverse/test/test_humanbase_tool.py +20 -0
  151. tooluniverse/test/test_idmap_tools.py +61 -0
  152. tooluniverse/test/test_mcp_server.py +211 -0
  153. tooluniverse/test/test_mcp_tool.py +247 -0
  154. tooluniverse/test/test_medlineplus.py +220 -0
  155. tooluniverse/test/test_openalex_tool.py +32 -0
  156. tooluniverse/test/test_opentargets.py +28 -0
  157. tooluniverse/test/test_pubchem_tool.py +116 -0
  158. tooluniverse/test/test_pubtator_tool.py +37 -0
  159. tooluniverse/test/test_rcsb_pdb_tool.py +86 -0
  160. tooluniverse/test/test_reactome.py +54 -0
  161. tooluniverse/test/test_semantic_scholar_tool.py +24 -0
  162. tooluniverse/test/test_software_tools.py +147 -0
  163. tooluniverse/test/test_tool_description_optimizer.py +49 -0
  164. tooluniverse/test/test_tool_finder.py +26 -0
  165. tooluniverse/test/test_tool_finder_llm.py +252 -0
  166. tooluniverse/test/test_tools_find.py +195 -0
  167. tooluniverse/test/test_uniprot_tools.py +74 -0
  168. tooluniverse/test/test_uspto_tool.py +72 -0
  169. tooluniverse/test/test_xml_tool.py +113 -0
  170. tooluniverse/tool_finder_embedding.py +267 -0
  171. tooluniverse/tool_finder_keyword.py +693 -0
  172. tooluniverse/tool_finder_llm.py +699 -0
  173. tooluniverse/tool_graph_web_ui.py +955 -0
  174. tooluniverse/tool_registry.py +416 -0
  175. tooluniverse/uniprot_tool.py +155 -0
  176. tooluniverse/url_tool.py +253 -0
  177. tooluniverse/uspto_tool.py +240 -0
  178. tooluniverse/utils.py +369 -41
  179. tooluniverse/xml_tool.py +369 -0
  180. tooluniverse-1.0.0.dist-info/METADATA +377 -0
  181. tooluniverse-1.0.0.dist-info/RECORD +186 -0
  182. tooluniverse-1.0.0.dist-info/entry_points.txt +9 -0
  183. tooluniverse/generate_mcp_tools.py +0 -113
  184. tooluniverse/mcp_server.py +0 -3340
  185. tooluniverse-0.2.0.dist-info/METADATA +0 -139
  186. tooluniverse-0.2.0.dist-info/RECORD +0 -21
  187. tooluniverse-0.2.0.dist-info/entry_points.txt +0 -4
  188. {tooluniverse-0.2.0.dist-info → tooluniverse-1.0.0.dist-info}/WHEEL +0 -0
  189. {tooluniverse-0.2.0.dist-info → tooluniverse-1.0.0.dist-info}/licenses/LICENSE +0 -0
  190. {tooluniverse-0.2.0.dist-info → tooluniverse-1.0.0.dist-info}/top_level.txt +0 -0
@@ -2,9 +2,9 @@ from graphql import build_schema
2
2
  from graphql.language import parse
3
3
  from graphql.validation import validate
4
4
  from .base_tool import BaseTool
5
+ from .tool_registry import register_tool
5
6
  import requests
6
7
  import copy
7
- import json
8
8
 
9
9
 
10
10
  def validate_query(query_str, schema_str):
@@ -22,39 +22,49 @@ def validate_query(query_str, schema_str):
22
22
  return True
23
23
  else:
24
24
  # Collect and return the validation errors
25
- error_messages = '\n'.join(str(error)
26
- for error in validation_errors)
25
+ error_messages = "\n".join(str(error) for error in validation_errors)
27
26
  return f"Query validation errors:\n{error_messages}"
28
27
  except Exception as e:
29
28
  return f"An error occurred during validation: {str(e)}"
30
29
 
30
+
31
31
  def remove_none_and_empty_values(json_obj):
32
32
  """Remove all key-value pairs where the value is None or an empty list"""
33
33
  if isinstance(json_obj, dict):
34
- return {k: remove_none_and_empty_values(v) for k, v in json_obj.items() if v is not None and v != []}
34
+ return {
35
+ k: remove_none_and_empty_values(v)
36
+ for k, v in json_obj.items()
37
+ if v is not None and v != []
38
+ }
35
39
  elif isinstance(json_obj, list):
36
- return [remove_none_and_empty_values(item) for item in json_obj if item is not None and item != []]
40
+ return [
41
+ remove_none_and_empty_values(item)
42
+ for item in json_obj
43
+ if item is not None and item != []
44
+ ]
37
45
  else:
38
46
  return json_obj
39
47
 
48
+
40
49
  def execute_query(endpoint_url, query, variables=None):
41
50
  response = requests.post(
42
- endpoint_url, json={'query': query, 'variables': variables})
51
+ endpoint_url, json={"query": query, "variables": variables}
52
+ )
43
53
  try:
44
54
  result = response.json()
45
55
  # result = json.dumps(result, ensure_ascii=False)
46
56
  result = remove_none_and_empty_values(result)
47
57
  # Check if the response contains errors
48
- if 'errors' in result:
49
- print("Invalid Query: ", result['errors'])
58
+ if "errors" in result:
59
+ print("Invalid Query: ", result["errors"])
50
60
  return None
51
- # Check if the data field is empty
52
- elif not result.get('data') or all(not v for v in result['data'].values()):
61
+ # Check if the data field is empty
62
+ elif not result.get("data") or all(not v for v in result["data"].values()):
53
63
  print("No data returned")
54
64
  return None
55
65
  else:
56
66
  return result
57
- except requests.exceptions.JSONDecodeError as e:
67
+ except requests.exceptions.JSONDecodeError:
58
68
  print("JSONDecodeError: Could not decode the response as JSON")
59
69
  return None
60
70
 
@@ -63,60 +73,172 @@ class GraphQLTool(BaseTool):
63
73
  def __init__(self, tool_config, endpoint_url):
64
74
  super().__init__(tool_config)
65
75
  self.endpoint_url = endpoint_url
66
- self.query_schema = tool_config['query_schema']
67
- self.parameters = tool_config['parameter']['properties']
76
+ self.query_schema = tool_config["query_schema"]
77
+ self.parameters = tool_config["parameter"]["properties"]
68
78
  self.default_size = 5
69
79
 
70
80
  def run(self, arguments):
71
81
  arguments = copy.deepcopy(arguments)
72
- if 'size' in self.parameters and 'size' not in arguments:
73
- arguments['size'] = 5
74
- return execute_query(endpoint_url=self.endpoint_url, query=self.query_schema, variables=arguments)
82
+ if "size" in self.parameters and "size" not in arguments:
83
+ arguments["size"] = 5
84
+ return execute_query(
85
+ endpoint_url=self.endpoint_url, query=self.query_schema, variables=arguments
86
+ )
75
87
 
76
88
 
89
+ @register_tool("OpenTarget")
77
90
  class OpentargetTool(GraphQLTool):
78
91
  def __init__(self, tool_config):
79
- endpoint_url = 'https://api.platform.opentargets.org/api/v4/graphql'
92
+ endpoint_url = "https://api.platform.opentargets.org/api/v4/graphql"
80
93
  super().__init__(tool_config, endpoint_url)
81
-
94
+
82
95
  def run(self, arguments):
83
- for each_arg, arg_value in arguments.items(): # opentarget api cannot handle '-' in the arguments
84
- if isinstance(arg_value, str):
85
- if '-' in arg_value:
86
- arguments[each_arg] = arg_value.replace('-', ' ')
87
- return super().run(arguments)
88
-
96
+ # First try without modifying '-'
97
+ result = super().run(arguments)
98
+
99
+ # If no results, try with '-' replaced by ' '
100
+ if result is None:
101
+ if "drugName" in arguments and isinstance(arguments["drugName"], str):
102
+ arguments["drugName"] = arguments["drugName"].split("-")[0]
103
+ modified_arguments = copy.deepcopy(arguments)
104
+ for each_arg, arg_value in modified_arguments.items():
105
+ if isinstance(arg_value, str) and "-" in arg_value:
106
+ modified_arguments[each_arg] = arg_value.replace("-", " ")
107
+ result = super().run(modified_arguments)
108
+ return result
89
109
 
110
+ return result
111
+
112
+
113
+ @register_tool("OpentargetToolDrugNameMatch")
90
114
  class OpentargetToolDrugNameMatch(GraphQLTool):
91
115
  def __init__(self, tool_config, drug_generic_tool=None):
92
- endpoint_url = 'https://api.platform.opentargets.org/api/v4/graphql'
116
+ endpoint_url = "https://api.platform.opentargets.org/api/v4/graphql"
93
117
  self.drug_generic_tool = drug_generic_tool
94
- self.possible_drug_name_args = ['drugName']
118
+ self.possible_drug_name_args = ["drugName"]
95
119
  super().__init__(tool_config, endpoint_url)
96
120
 
97
121
  def run(self, arguments):
98
122
  arguments = copy.deepcopy(arguments)
99
- results = execute_query(endpoint_url=self.endpoint_url, query=self.query_schema, variables=arguments)
123
+ results = execute_query(
124
+ endpoint_url=self.endpoint_url, query=self.query_schema, variables=arguments
125
+ )
100
126
  if results is None:
101
- print("No results found for the drug brand name. Trying with the generic name.")
127
+ print(
128
+ "No results found for the drug brand name. Trying with the generic name."
129
+ )
102
130
  name_arguments = {}
103
131
  for each_args in self.possible_drug_name_args:
104
132
  if each_args in arguments:
105
- name_arguments['drug_name'] = arguments[each_args]
133
+ name_arguments["drug_name"] = arguments[each_args]
106
134
  break
107
- if len(name_arguments)==0:
135
+ if len(name_arguments) == 0:
108
136
  print("No drug name found in the arguments.")
109
137
  return None
110
138
  drug_name_results = self.drug_generic_tool.run(name_arguments)
111
- if drug_name_results is not None and 'openfda.generic_name' in drug_name_results:
112
- arguments[each_args] = drug_name_results['openfda.generic_name']
113
- print("Found generic name. Trying with the generic name: ", arguments[each_args])
114
- results = execute_query(endpoint_url=self.endpoint_url, query=self.query_schema, variables=arguments)
139
+ if (
140
+ drug_name_results is not None
141
+ and "openfda.generic_name" in drug_name_results
142
+ ):
143
+ arguments[each_args] = drug_name_results["openfda.generic_name"]
144
+ print(
145
+ "Found generic name. Trying with the generic name: ",
146
+ arguments[each_args],
147
+ )
148
+ results = execute_query(
149
+ endpoint_url=self.endpoint_url,
150
+ query=self.query_schema,
151
+ variables=arguments,
152
+ )
115
153
  return results
116
-
117
154
 
118
155
 
156
+ @register_tool("OpenTargetGenetics")
119
157
  class OpentargetGeneticsTool(GraphQLTool):
120
158
  def __init__(self, tool_config):
121
- endpoint_url = 'https://api.genetics.opentargets.org/graphql'
159
+ endpoint_url = "https://api.genetics.opentargets.org/graphql"
122
160
  super().__init__(tool_config, endpoint_url)
161
+
162
+
163
+ @register_tool("DiseaseTargetScoreTool")
164
+ class DiseaseTargetScoreTool(GraphQLTool):
165
+ """Tool to extract disease-target association scores from specific data sources"""
166
+
167
+ def __init__(self, tool_config, datasource_id=None):
168
+ endpoint_url = "https://api.platform.opentargets.org/api/v4/graphql"
169
+ # Get datasource_id from config if not provided as parameter
170
+ self.datasource_id = datasource_id or tool_config.get("datasource_id")
171
+ super().__init__(tool_config, endpoint_url)
172
+
173
+ def run(self, arguments):
174
+ """
175
+ Extract disease-target scores for a specific datasource
176
+ Arguments should contain: efoId, datasourceId (optional), pageSize (optional)
177
+ """
178
+ arguments = copy.deepcopy(arguments)
179
+ efo_id = arguments.get("efoId")
180
+ datasource_id = arguments.get("datasourceId", self.datasource_id)
181
+ page_size = arguments.get("pageSize", 100)
182
+
183
+ if not efo_id:
184
+ return {"error": "efoId is required"}
185
+ if not datasource_id:
186
+ return {"error": "datasourceId is required"}
187
+
188
+ results = []
189
+ page_index = 0
190
+ total_fetched = 0
191
+ total_count = None
192
+ disease_info = None
193
+
194
+ while True:
195
+ variables = {"efoId": efo_id, "index": page_index, "size": page_size}
196
+
197
+ response_data = execute_query(
198
+ self.endpoint_url, self.query_schema, variables
199
+ )
200
+ if not response_data or "data" not in response_data:
201
+ break
202
+
203
+ disease_data = response_data["data"]["disease"]
204
+ if not disease_data:
205
+ break
206
+
207
+ if disease_info is None:
208
+ disease_info = {
209
+ "disease_id": disease_data["id"],
210
+ "disease_name": disease_data["name"],
211
+ }
212
+
213
+ rows = disease_data["associatedTargets"]["rows"]
214
+ if total_count is None:
215
+ total_count = disease_data["associatedTargets"]["count"]
216
+
217
+ for row in rows:
218
+ symbol = row["target"]["approvedSymbol"]
219
+ target_id = row["target"]["id"]
220
+ score_entry = next(
221
+ (ds for ds in row["datasourceScores"] if ds["id"] == datasource_id),
222
+ None,
223
+ )
224
+ if score_entry:
225
+ results.append(
226
+ {
227
+ "target_symbol": symbol,
228
+ "target_id": target_id,
229
+ "datasource": datasource_id,
230
+ "score": score_entry["score"],
231
+ }
232
+ )
233
+
234
+ total_fetched += len(rows)
235
+ if total_fetched >= total_count or len(rows) == 0:
236
+ break
237
+ page_index += 1
238
+
239
+ return {
240
+ "disease_info": disease_info,
241
+ "datasource": datasource_id,
242
+ "total_targets_with_scores": len(results),
243
+ "target_scores": results,
244
+ }
@@ -0,0 +1,358 @@
1
+ import requests
2
+ from typing import Dict, Any, Optional
3
+ from .base_tool import BaseTool
4
+ from .tool_registry import register_tool
5
+
6
+
7
+ class GWASRESTTool(BaseTool):
8
+ """Base class for GWAS Catalog REST API tools."""
9
+
10
+ def __init__(self, tool_config):
11
+ super().__init__(tool_config)
12
+ self.base_url = "https://www.ebi.ac.uk/gwas/rest/api"
13
+ self.endpoint = "" # Will be set by subclasses
14
+
15
+ def _make_request(
16
+ self, endpoint: str, params: Optional[Dict] = None
17
+ ) -> Dict[str, Any]:
18
+ """Make a request to the GWAS Catalog API."""
19
+ url = f"{self.base_url}{endpoint}"
20
+ try:
21
+ response = requests.get(url, params=params, timeout=30)
22
+ response.raise_for_status()
23
+ return response.json()
24
+ except requests.exceptions.RequestException as e:
25
+ return {"error": f"Request failed: {str(e)}"}
26
+
27
+ def _extract_embedded_data(
28
+ self, data: Dict[str, Any], data_type: str
29
+ ) -> Dict[str, Any]:
30
+ """Extract data from the _embedded structure and add metadata."""
31
+ if "error" in data:
32
+ return data
33
+
34
+ result: Dict[str, Any] = {"data": [], "metadata": {}}
35
+ metadata: Dict[str, Any] = {}
36
+
37
+ # Extract the main data from _embedded
38
+ if "_embedded" in data and data_type in data["_embedded"]:
39
+ result["data"] = data["_embedded"][data_type]
40
+
41
+ # Extract pagination metadata
42
+ if "page" in data:
43
+ metadata["pagination"] = data["page"]
44
+
45
+ # Extract links metadata
46
+ if "_links" in data:
47
+ metadata["links"] = data["_links"]
48
+
49
+ if metadata:
50
+ result["metadata"] = metadata
51
+
52
+ # If no _embedded structure, return the original data
53
+ if not result["data"]:
54
+ result["data"] = data
55
+
56
+ return result
57
+
58
+ def run(self, arguments: Dict[str, Any]) -> Dict[str, Any]:
59
+ """Execute the tool with given arguments."""
60
+ return self._make_request(self.endpoint, arguments)
61
+
62
+
63
+ @register_tool("GWASAssociationSearch")
64
+ class GWASAssociationSearch(GWASRESTTool):
65
+ """Search for GWAS associations by various criteria."""
66
+
67
+ def __init__(self, tool_config):
68
+ super().__init__(tool_config)
69
+ self.endpoint = "/v2/associations"
70
+
71
+ def run(self, arguments: Dict[str, Any]) -> Dict[str, Any]:
72
+ """Search for associations with optional filters."""
73
+ params = {}
74
+
75
+ # Handle various search parameters based on examples
76
+ if "efo_trait" in arguments:
77
+ params["efo_trait"] = arguments["efo_trait"]
78
+ if "rs_id" in arguments:
79
+ params["rs_id"] = arguments["rs_id"]
80
+ if "accession_id" in arguments:
81
+ params["accession_id"] = arguments["accession_id"]
82
+ if "sort" in arguments:
83
+ params["sort"] = arguments["sort"]
84
+ if "direction" in arguments:
85
+ params["direction"] = arguments["direction"]
86
+ if "size" in arguments:
87
+ params["size"] = arguments["size"]
88
+ if "page" in arguments:
89
+ params["page"] = arguments["page"]
90
+
91
+ data = self._make_request(self.endpoint, params)
92
+ return self._extract_embedded_data(data, "associations")
93
+
94
+
95
+ @register_tool("GWASStudySearch")
96
+ class GWASStudySearch(GWASRESTTool):
97
+ """Search for GWAS studies by various criteria."""
98
+
99
+ def __init__(self, tool_config):
100
+ super().__init__(tool_config)
101
+ self.endpoint = "/v2/studies"
102
+
103
+ def run(self, arguments: Dict[str, Any]) -> Dict[str, Any]:
104
+ """Search for studies with optional filters."""
105
+ params = {}
106
+
107
+ if "efo_trait" in arguments:
108
+ params["efo_trait"] = arguments["efo_trait"]
109
+ if "disease_trait" in arguments:
110
+ params["disease_trait"] = arguments["disease_trait"]
111
+ if "cohort" in arguments:
112
+ params["cohort"] = arguments["cohort"]
113
+ if "gxe" in arguments:
114
+ params["gxe"] = arguments["gxe"]
115
+ if "full_pvalue_set" in arguments:
116
+ params["full_pvalue_set"] = arguments["full_pvalue_set"]
117
+ if "size" in arguments:
118
+ params["size"] = arguments["size"]
119
+ if "page" in arguments:
120
+ params["page"] = arguments["page"]
121
+
122
+ data = self._make_request(self.endpoint, params)
123
+ return self._extract_embedded_data(data, "studies")
124
+
125
+
126
+ @register_tool("GWASSNPSearch")
127
+ class GWASSNPSearch(GWASRESTTool):
128
+ """Search for GWAS single nucleotide polymorphisms (SNPs)."""
129
+
130
+ def __init__(self, tool_config):
131
+ super().__init__(tool_config)
132
+ self.endpoint = "/v2/single-nucleotide-polymorphisms"
133
+
134
+ def run(self, arguments: Dict[str, Any]) -> Dict[str, Any]:
135
+ """Search for SNPs with optional filters."""
136
+ params = {}
137
+
138
+ if "rs_id" in arguments:
139
+ params["rs_id"] = arguments["rs_id"]
140
+ if "mapped_gene" in arguments:
141
+ params["mapped_gene"] = arguments["mapped_gene"]
142
+ if "size" in arguments:
143
+ params["size"] = arguments["size"]
144
+ if "page" in arguments:
145
+ params["page"] = arguments["page"]
146
+
147
+ data = self._make_request(self.endpoint, params)
148
+ return self._extract_embedded_data(data, "snps")
149
+
150
+
151
+ # Get by ID tools
152
+ @register_tool("GWASAssociationByID")
153
+ class GWASAssociationByID(GWASRESTTool):
154
+ """Get a specific GWAS association by its ID."""
155
+
156
+ def __init__(self, tool_config):
157
+ super().__init__(tool_config)
158
+ self.endpoint = "/v2/associations"
159
+
160
+ def run(self, arguments: Dict[str, Any]) -> Dict[str, Any]:
161
+ """Get association by ID."""
162
+ if "association_id" not in arguments:
163
+ return {"error": "association_id is required"}
164
+
165
+ association_id = arguments["association_id"]
166
+ return self._make_request(f"{self.endpoint}/{association_id}")
167
+
168
+
169
+ @register_tool("GWASStudyByID")
170
+ class GWASStudyByID(GWASRESTTool):
171
+ """Get a specific GWAS study by its ID."""
172
+
173
+ def __init__(self, tool_config):
174
+ super().__init__(tool_config)
175
+ self.endpoint = "/v2/studies"
176
+
177
+ def run(self, arguments: Dict[str, Any]) -> Dict[str, Any]:
178
+ """Get study by ID."""
179
+ if "study_id" not in arguments:
180
+ return {"error": "study_id is required"}
181
+
182
+ study_id = arguments["study_id"]
183
+ return self._make_request(f"{self.endpoint}/{study_id}")
184
+
185
+
186
+ @register_tool("GWASSNPByID")
187
+ class GWASSNPByID(GWASRESTTool):
188
+ """Get a specific GWAS SNP by its rs ID."""
189
+
190
+ def __init__(self, tool_config):
191
+ super().__init__(tool_config)
192
+ self.endpoint = "/v2/single-nucleotide-polymorphisms"
193
+
194
+ def run(self, arguments: Dict[str, Any]) -> Dict[str, Any]:
195
+ """Get SNP by rs ID."""
196
+ if "rs_id" not in arguments:
197
+ return {"error": "rs_id is required"}
198
+
199
+ rs_id = arguments["rs_id"]
200
+ return self._make_request(f"{self.endpoint}/{rs_id}")
201
+
202
+
203
+ # Specialized search tools based on common use cases from examples
204
+ @register_tool("GWASVariantsForTrait")
205
+ class GWASVariantsForTrait(GWASRESTTool):
206
+ """Get all variants associated with a specific trait."""
207
+
208
+ def __init__(self, tool_config):
209
+ super().__init__(tool_config)
210
+ self.endpoint = "/v2/associations"
211
+
212
+ def run(self, arguments: Dict[str, Any]) -> Dict[str, Any]:
213
+ """Get variants for a trait with pagination support."""
214
+ if "efo_trait" not in arguments:
215
+ return {"error": "efo_trait is required"}
216
+
217
+ params = {
218
+ "efo_trait": arguments["efo_trait"],
219
+ "size": arguments.get("size", 200),
220
+ "page": arguments.get("page", 0),
221
+ }
222
+
223
+ data = self._make_request(self.endpoint, params)
224
+ return self._extract_embedded_data(data, "associations")
225
+
226
+
227
+ @register_tool("GWASAssociationsForTrait")
228
+ class GWASAssociationsForTrait(GWASRESTTool):
229
+ """Get all associations for a specific trait, sorted by p-value."""
230
+
231
+ def __init__(self, tool_config):
232
+ super().__init__(tool_config)
233
+ self.endpoint = "/v2/associations"
234
+
235
+ def run(self, arguments: Dict[str, Any]) -> Dict[str, Any]:
236
+ """Get associations for a trait, sorted by significance."""
237
+ if "efo_trait" not in arguments:
238
+ return {"error": "efo_trait is required"}
239
+
240
+ params = {
241
+ "efo_trait": arguments["efo_trait"],
242
+ "sort": "p_value",
243
+ "direction": "asc",
244
+ "size": arguments.get("size", 40),
245
+ "page": arguments.get("page", 0),
246
+ }
247
+
248
+ data = self._make_request(self.endpoint, params)
249
+ return self._extract_embedded_data(data, "associations")
250
+
251
+
252
+ @register_tool("GWASAssociationsForSNP")
253
+ class GWASAssociationsForSNP(GWASRESTTool):
254
+ """Get all associations for a specific SNP."""
255
+
256
+ def __init__(self, tool_config):
257
+ super().__init__(tool_config)
258
+ self.endpoint = "/v2/associations"
259
+
260
+ def run(self, arguments: Dict[str, Any]) -> Dict[str, Any]:
261
+ """Get associations for a SNP."""
262
+ if "rs_id" not in arguments:
263
+ return {"error": "rs_id is required"}
264
+
265
+ params = {
266
+ "rs_id": arguments["rs_id"],
267
+ "size": arguments.get("size", 200),
268
+ "page": arguments.get("page", 0),
269
+ }
270
+
271
+ if "sort" in arguments:
272
+ params["sort"] = arguments["sort"]
273
+ if "direction" in arguments:
274
+ params["direction"] = arguments["direction"]
275
+
276
+ data = self._make_request(self.endpoint, params)
277
+ return self._extract_embedded_data(data, "associations")
278
+
279
+
280
+ @register_tool("GWASStudiesForTrait")
281
+ class GWASStudiesForTrait(GWASRESTTool):
282
+ """Get studies for a specific trait with optional filters."""
283
+
284
+ def __init__(self, tool_config):
285
+ super().__init__(tool_config)
286
+ self.endpoint = "/v2/studies"
287
+
288
+ def run(self, arguments: Dict[str, Any]) -> Dict[str, Any]:
289
+ """Get studies for a trait with optional filters."""
290
+ if "efo_trait" not in arguments and "disease_trait" not in arguments:
291
+ return {"error": "efo_trait or disease_trait is required"}
292
+
293
+ params = {
294
+ "size": arguments.get("size", 200),
295
+ "page": arguments.get("page", 0),
296
+ }
297
+
298
+ if "efo_trait" in arguments:
299
+ params["efo_trait"] = arguments["efo_trait"]
300
+ if "disease_trait" in arguments:
301
+ params["disease_trait"] = arguments["disease_trait"]
302
+ if "cohort" in arguments:
303
+ params["cohort"] = arguments["cohort"]
304
+ if "gxe" in arguments:
305
+ params["gxe"] = arguments["gxe"]
306
+ if "full_pvalue_set" in arguments:
307
+ params["full_pvalue_set"] = arguments["full_pvalue_set"]
308
+
309
+ data = self._make_request(self.endpoint, params)
310
+ return self._extract_embedded_data(data, "studies")
311
+
312
+
313
+ @register_tool("GWASSNPsForGene")
314
+ class GWASSNPsForGene(GWASRESTTool):
315
+ """Get SNPs mapped to a specific gene."""
316
+
317
+ def __init__(self, tool_config):
318
+ super().__init__(tool_config)
319
+ self.endpoint = "/v2/single-nucleotide-polymorphisms"
320
+
321
+ def run(self, arguments: Dict[str, Any]) -> Dict[str, Any]:
322
+ """Get SNPs for a gene."""
323
+ if "mapped_gene" not in arguments:
324
+ return {"error": "mapped_gene is required"}
325
+
326
+ params = {
327
+ "mapped_gene": arguments["mapped_gene"],
328
+ "size": arguments.get("size", 10000),
329
+ "page": arguments.get("page", 0),
330
+ }
331
+
332
+ data = self._make_request(self.endpoint, params)
333
+ return self._extract_embedded_data(data, "snps")
334
+
335
+
336
+ @register_tool("GWASAssociationsForStudy")
337
+ class GWASAssociationsForStudy(GWASRESTTool):
338
+ """Get all associations for a specific study."""
339
+
340
+ def __init__(self, tool_config):
341
+ super().__init__(tool_config)
342
+ self.endpoint = "/v2/associations"
343
+
344
+ def run(self, arguments: Dict[str, Any]) -> Dict[str, Any]:
345
+ """Get associations for a study."""
346
+ if "accession_id" not in arguments:
347
+ return {"error": "accession_id is required"}
348
+
349
+ params = {
350
+ "accession_id": arguments["accession_id"],
351
+ "sort": "p_value",
352
+ "direction": "asc",
353
+ "size": arguments.get("size", 200),
354
+ "page": arguments.get("page", 0),
355
+ }
356
+
357
+ data = self._make_request(self.endpoint, params)
358
+ return self._extract_embedded_data(data, "associations")