aurelian 0.3.2__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.
Files changed (254) hide show
  1. aurelian/__init__.py +9 -0
  2. aurelian/agents/__init__.py +0 -0
  3. aurelian/agents/amigo/__init__.py +3 -0
  4. aurelian/agents/amigo/amigo_agent.py +77 -0
  5. aurelian/agents/amigo/amigo_config.py +85 -0
  6. aurelian/agents/amigo/amigo_evals.py +73 -0
  7. aurelian/agents/amigo/amigo_gradio.py +52 -0
  8. aurelian/agents/amigo/amigo_mcp.py +152 -0
  9. aurelian/agents/amigo/amigo_tools.py +152 -0
  10. aurelian/agents/biblio/__init__.py +42 -0
  11. aurelian/agents/biblio/biblio_agent.py +94 -0
  12. aurelian/agents/biblio/biblio_config.py +40 -0
  13. aurelian/agents/biblio/biblio_gradio.py +67 -0
  14. aurelian/agents/biblio/biblio_mcp.py +115 -0
  15. aurelian/agents/biblio/biblio_tools.py +164 -0
  16. aurelian/agents/biblio_agent.py +46 -0
  17. aurelian/agents/checklist/__init__.py +44 -0
  18. aurelian/agents/checklist/checklist_agent.py +85 -0
  19. aurelian/agents/checklist/checklist_config.py +28 -0
  20. aurelian/agents/checklist/checklist_gradio.py +70 -0
  21. aurelian/agents/checklist/checklist_mcp.py +86 -0
  22. aurelian/agents/checklist/checklist_tools.py +141 -0
  23. aurelian/agents/checklist/content/checklists.yaml +7 -0
  24. aurelian/agents/checklist/content/streams.csv +136 -0
  25. aurelian/agents/checklist_agent.py +40 -0
  26. aurelian/agents/chemistry/__init__.py +3 -0
  27. aurelian/agents/chemistry/chemistry_agent.py +46 -0
  28. aurelian/agents/chemistry/chemistry_config.py +71 -0
  29. aurelian/agents/chemistry/chemistry_evals.py +79 -0
  30. aurelian/agents/chemistry/chemistry_gradio.py +50 -0
  31. aurelian/agents/chemistry/chemistry_mcp.py +120 -0
  32. aurelian/agents/chemistry/chemistry_tools.py +121 -0
  33. aurelian/agents/chemistry/image_agent.py +15 -0
  34. aurelian/agents/d4d/__init__.py +30 -0
  35. aurelian/agents/d4d/d4d_agent.py +72 -0
  36. aurelian/agents/d4d/d4d_config.py +46 -0
  37. aurelian/agents/d4d/d4d_gradio.py +58 -0
  38. aurelian/agents/d4d/d4d_mcp.py +71 -0
  39. aurelian/agents/d4d/d4d_tools.py +157 -0
  40. aurelian/agents/d4d_agent.py +64 -0
  41. aurelian/agents/diagnosis/__init__.py +33 -0
  42. aurelian/agents/diagnosis/diagnosis_agent.py +53 -0
  43. aurelian/agents/diagnosis/diagnosis_config.py +48 -0
  44. aurelian/agents/diagnosis/diagnosis_evals.py +76 -0
  45. aurelian/agents/diagnosis/diagnosis_gradio.py +52 -0
  46. aurelian/agents/diagnosis/diagnosis_mcp.py +141 -0
  47. aurelian/agents/diagnosis/diagnosis_tools.py +204 -0
  48. aurelian/agents/diagnosis_agent.py +28 -0
  49. aurelian/agents/draw/__init__.py +3 -0
  50. aurelian/agents/draw/draw_agent.py +39 -0
  51. aurelian/agents/draw/draw_config.py +26 -0
  52. aurelian/agents/draw/draw_gradio.py +50 -0
  53. aurelian/agents/draw/draw_mcp.py +94 -0
  54. aurelian/agents/draw/draw_tools.py +100 -0
  55. aurelian/agents/draw/judge_agent.py +18 -0
  56. aurelian/agents/filesystem/__init__.py +0 -0
  57. aurelian/agents/filesystem/filesystem_config.py +27 -0
  58. aurelian/agents/filesystem/filesystem_gradio.py +49 -0
  59. aurelian/agents/filesystem/filesystem_mcp.py +89 -0
  60. aurelian/agents/filesystem/filesystem_tools.py +95 -0
  61. aurelian/agents/filesystem/py.typed +0 -0
  62. aurelian/agents/github/__init__.py +0 -0
  63. aurelian/agents/github/github_agent.py +83 -0
  64. aurelian/agents/github/github_cli.py +248 -0
  65. aurelian/agents/github/github_config.py +22 -0
  66. aurelian/agents/github/github_gradio.py +152 -0
  67. aurelian/agents/github/github_mcp.py +252 -0
  68. aurelian/agents/github/github_tools.py +408 -0
  69. aurelian/agents/github/github_tools.py.tmp +413 -0
  70. aurelian/agents/goann/__init__.py +13 -0
  71. aurelian/agents/goann/documents/Transcription_Factors_Annotation_Guidelines.md +1000 -0
  72. aurelian/agents/goann/documents/Transcription_Factors_Annotation_Guidelines.pdf +0 -0
  73. aurelian/agents/goann/documents/Transcription_Factors_Annotation_Guidelines_Paper.md +693 -0
  74. aurelian/agents/goann/documents/Transcription_Factors_Annotation_Guidelines_Paper.pdf +0 -0
  75. aurelian/agents/goann/goann_agent.py +90 -0
  76. aurelian/agents/goann/goann_config.py +90 -0
  77. aurelian/agents/goann/goann_evals.py +104 -0
  78. aurelian/agents/goann/goann_gradio.py +62 -0
  79. aurelian/agents/goann/goann_mcp.py +0 -0
  80. aurelian/agents/goann/goann_tools.py +65 -0
  81. aurelian/agents/gocam/__init__.py +43 -0
  82. aurelian/agents/gocam/documents/DNA-binding transcription factor activity annotation guidelines.docx +0 -0
  83. aurelian/agents/gocam/documents/DNA-binding transcription factor activity annotation guidelines.pdf +0 -0
  84. aurelian/agents/gocam/documents/DNA-binding_transcription_factor_activity_annotation_guidelines.md +100 -0
  85. aurelian/agents/gocam/documents/E3 ubiquitin ligases.docx +0 -0
  86. aurelian/agents/gocam/documents/E3 ubiquitin ligases.pdf +0 -0
  87. aurelian/agents/gocam/documents/E3_ubiquitin_ligases.md +134 -0
  88. aurelian/agents/gocam/documents/GO-CAM annotation guidelines README.docx +0 -0
  89. aurelian/agents/gocam/documents/GO-CAM annotation guidelines README.pdf +0 -0
  90. aurelian/agents/gocam/documents/GO-CAM modelling guidelines TO DO.docx +0 -0
  91. aurelian/agents/gocam/documents/GO-CAM modelling guidelines TO DO.pdf +0 -0
  92. aurelian/agents/gocam/documents/GO-CAM_annotation_guidelines_README.md +1 -0
  93. aurelian/agents/gocam/documents/GO-CAM_modelling_guidelines_TO_DO.md +3 -0
  94. aurelian/agents/gocam/documents/How to annotate complexes in GO-CAM.docx +0 -0
  95. aurelian/agents/gocam/documents/How to annotate complexes in GO-CAM.pdf +0 -0
  96. aurelian/agents/gocam/documents/How to annotate molecular adaptors.docx +0 -0
  97. aurelian/agents/gocam/documents/How to annotate molecular adaptors.pdf +0 -0
  98. aurelian/agents/gocam/documents/How to annotate sequestering proteins.docx +0 -0
  99. aurelian/agents/gocam/documents/How to annotate sequestering proteins.pdf +0 -0
  100. aurelian/agents/gocam/documents/How_to_annotate_complexes_in_GO-CAM.md +29 -0
  101. aurelian/agents/gocam/documents/How_to_annotate_molecular_adaptors.md +31 -0
  102. aurelian/agents/gocam/documents/How_to_annotate_sequestering_proteins.md +42 -0
  103. aurelian/agents/gocam/documents/Molecular adaptor activity.docx +0 -0
  104. aurelian/agents/gocam/documents/Molecular adaptor activity.pdf +0 -0
  105. aurelian/agents/gocam/documents/Molecular carrier activity.docx +0 -0
  106. aurelian/agents/gocam/documents/Molecular carrier activity.pdf +0 -0
  107. aurelian/agents/gocam/documents/Molecular_adaptor_activity.md +51 -0
  108. aurelian/agents/gocam/documents/Molecular_carrier_activity.md +41 -0
  109. aurelian/agents/gocam/documents/Protein sequestering activity.docx +0 -0
  110. aurelian/agents/gocam/documents/Protein sequestering activity.pdf +0 -0
  111. aurelian/agents/gocam/documents/Protein_sequestering_activity.md +50 -0
  112. aurelian/agents/gocam/documents/Signaling receptor activity annotation guidelines.docx +0 -0
  113. aurelian/agents/gocam/documents/Signaling receptor activity annotation guidelines.pdf +0 -0
  114. aurelian/agents/gocam/documents/Signaling_receptor_activity_annotation_guidelines.md +187 -0
  115. aurelian/agents/gocam/documents/Transcription coregulator activity.docx +0 -0
  116. aurelian/agents/gocam/documents/Transcription coregulator activity.pdf +0 -0
  117. aurelian/agents/gocam/documents/Transcription_coregulator_activity.md +36 -0
  118. aurelian/agents/gocam/documents/Transporter activity annotation annotation guidelines.docx +0 -0
  119. aurelian/agents/gocam/documents/Transporter activity annotation annotation guidelines.pdf +0 -0
  120. aurelian/agents/gocam/documents/Transporter_activity_annotation_annotation_guidelines.md +43 -0
  121. Regulatory Processes in GO-CAM.docx +0 -0
  122. Regulatory Processes in GO-CAM.pdf +0 -0
  123. aurelian/agents/gocam/documents/WIP_-_Regulation_and_Regulatory_Processes_in_GO-CAM.md +31 -0
  124. aurelian/agents/gocam/documents/md/DNA-binding_transcription_factor_activity_annotation_guidelines.md +131 -0
  125. aurelian/agents/gocam/documents/md/E3_ubiquitin_ligases.md +166 -0
  126. aurelian/agents/gocam/documents/md/GO-CAM_annotation_guidelines_README.md +1 -0
  127. aurelian/agents/gocam/documents/md/GO-CAM_modelling_guidelines_TO_DO.md +5 -0
  128. aurelian/agents/gocam/documents/md/How_to_annotate_complexes_in_GO-CAM.md +28 -0
  129. aurelian/agents/gocam/documents/md/How_to_annotate_molecular_adaptors.md +19 -0
  130. aurelian/agents/gocam/documents/md/How_to_annotate_sequestering_proteins.md +38 -0
  131. aurelian/agents/gocam/documents/md/Molecular_adaptor_activity.md +52 -0
  132. aurelian/agents/gocam/documents/md/Molecular_carrier_activity.md +59 -0
  133. aurelian/agents/gocam/documents/md/Protein_sequestering_activity.md +52 -0
  134. aurelian/agents/gocam/documents/md/Signaling_receptor_activity_annotation_guidelines.md +271 -0
  135. aurelian/agents/gocam/documents/md/Transcription_coregulator_activity.md +54 -0
  136. aurelian/agents/gocam/documents/md/Transporter_activity_annotation_annotation_guidelines.md +38 -0
  137. aurelian/agents/gocam/documents/md/WIP_-_Regulation_and_Regulatory_Processes_in_GO-CAM.md +39 -0
  138. aurelian/agents/gocam/documents/pandoc_md/Signaling_receptor_activity_annotation_guidelines.md +334 -0
  139. aurelian/agents/gocam/gocam_agent.py +240 -0
  140. aurelian/agents/gocam/gocam_config.py +85 -0
  141. aurelian/agents/gocam/gocam_curator_agent.py +46 -0
  142. aurelian/agents/gocam/gocam_evals.py +67 -0
  143. aurelian/agents/gocam/gocam_gradio.py +89 -0
  144. aurelian/agents/gocam/gocam_mcp.py +224 -0
  145. aurelian/agents/gocam/gocam_tools.py +294 -0
  146. aurelian/agents/linkml/__init__.py +0 -0
  147. aurelian/agents/linkml/linkml_agent.py +62 -0
  148. aurelian/agents/linkml/linkml_config.py +48 -0
  149. aurelian/agents/linkml/linkml_evals.py +66 -0
  150. aurelian/agents/linkml/linkml_gradio.py +45 -0
  151. aurelian/agents/linkml/linkml_mcp.py +186 -0
  152. aurelian/agents/linkml/linkml_tools.py +102 -0
  153. aurelian/agents/literature/__init__.py +3 -0
  154. aurelian/agents/literature/literature_agent.py +55 -0
  155. aurelian/agents/literature/literature_config.py +35 -0
  156. aurelian/agents/literature/literature_gradio.py +52 -0
  157. aurelian/agents/literature/literature_mcp.py +174 -0
  158. aurelian/agents/literature/literature_tools.py +182 -0
  159. aurelian/agents/monarch/__init__.py +25 -0
  160. aurelian/agents/monarch/monarch_agent.py +44 -0
  161. aurelian/agents/monarch/monarch_config.py +45 -0
  162. aurelian/agents/monarch/monarch_gradio.py +51 -0
  163. aurelian/agents/monarch/monarch_mcp.py +65 -0
  164. aurelian/agents/monarch/monarch_tools.py +113 -0
  165. aurelian/agents/oak/__init__.py +0 -0
  166. aurelian/agents/oak/oak_config.py +27 -0
  167. aurelian/agents/oak/oak_gradio.py +57 -0
  168. aurelian/agents/ontology_mapper/__init__.py +31 -0
  169. aurelian/agents/ontology_mapper/ontology_mapper_agent.py +56 -0
  170. aurelian/agents/ontology_mapper/ontology_mapper_config.py +50 -0
  171. aurelian/agents/ontology_mapper/ontology_mapper_evals.py +108 -0
  172. aurelian/agents/ontology_mapper/ontology_mapper_gradio.py +58 -0
  173. aurelian/agents/ontology_mapper/ontology_mapper_mcp.py +81 -0
  174. aurelian/agents/ontology_mapper/ontology_mapper_tools.py +147 -0
  175. aurelian/agents/phenopackets/__init__.py +3 -0
  176. aurelian/agents/phenopackets/phenopackets_agent.py +58 -0
  177. aurelian/agents/phenopackets/phenopackets_config.py +72 -0
  178. aurelian/agents/phenopackets/phenopackets_evals.py +99 -0
  179. aurelian/agents/phenopackets/phenopackets_gradio.py +55 -0
  180. aurelian/agents/phenopackets/phenopackets_mcp.py +178 -0
  181. aurelian/agents/phenopackets/phenopackets_tools.py +127 -0
  182. aurelian/agents/rag/__init__.py +40 -0
  183. aurelian/agents/rag/rag_agent.py +83 -0
  184. aurelian/agents/rag/rag_config.py +80 -0
  185. aurelian/agents/rag/rag_gradio.py +67 -0
  186. aurelian/agents/rag/rag_mcp.py +107 -0
  187. aurelian/agents/rag/rag_tools.py +189 -0
  188. aurelian/agents/rag_agent.py +54 -0
  189. aurelian/agents/robot/__init__.py +0 -0
  190. aurelian/agents/robot/assets/__init__.py +3 -0
  191. aurelian/agents/robot/assets/template.md +384 -0
  192. aurelian/agents/robot/robot_config.py +25 -0
  193. aurelian/agents/robot/robot_gradio.py +46 -0
  194. aurelian/agents/robot/robot_mcp.py +100 -0
  195. aurelian/agents/robot/robot_ontology_agent.py +139 -0
  196. aurelian/agents/robot/robot_tools.py +50 -0
  197. aurelian/agents/talisman/__init__.py +3 -0
  198. aurelian/agents/talisman/talisman_agent.py +126 -0
  199. aurelian/agents/talisman/talisman_config.py +66 -0
  200. aurelian/agents/talisman/talisman_gradio.py +50 -0
  201. aurelian/agents/talisman/talisman_mcp.py +168 -0
  202. aurelian/agents/talisman/talisman_tools.py +720 -0
  203. aurelian/agents/ubergraph/__init__.py +40 -0
  204. aurelian/agents/ubergraph/ubergraph_agent.py +71 -0
  205. aurelian/agents/ubergraph/ubergraph_config.py +79 -0
  206. aurelian/agents/ubergraph/ubergraph_gradio.py +48 -0
  207. aurelian/agents/ubergraph/ubergraph_mcp.py +69 -0
  208. aurelian/agents/ubergraph/ubergraph_tools.py +118 -0
  209. aurelian/agents/uniprot/__init__.py +37 -0
  210. aurelian/agents/uniprot/uniprot_agent.py +43 -0
  211. aurelian/agents/uniprot/uniprot_config.py +43 -0
  212. aurelian/agents/uniprot/uniprot_evals.py +99 -0
  213. aurelian/agents/uniprot/uniprot_gradio.py +48 -0
  214. aurelian/agents/uniprot/uniprot_mcp.py +168 -0
  215. aurelian/agents/uniprot/uniprot_tools.py +136 -0
  216. aurelian/agents/web/__init__.py +0 -0
  217. aurelian/agents/web/web_config.py +27 -0
  218. aurelian/agents/web/web_gradio.py +48 -0
  219. aurelian/agents/web/web_mcp.py +50 -0
  220. aurelian/agents/web/web_tools.py +108 -0
  221. aurelian/chat.py +23 -0
  222. aurelian/cli.py +800 -0
  223. aurelian/dependencies/__init__.py +0 -0
  224. aurelian/dependencies/workdir.py +78 -0
  225. aurelian/mcp/__init__.py +0 -0
  226. aurelian/mcp/amigo_mcp_test.py +86 -0
  227. aurelian/mcp/config_generator.py +123 -0
  228. aurelian/mcp/example_config.json +43 -0
  229. aurelian/mcp/generate_sample_config.py +37 -0
  230. aurelian/mcp/gocam_mcp_test.py +126 -0
  231. aurelian/mcp/linkml_mcp_tools.py +190 -0
  232. aurelian/mcp/mcp_discovery.py +87 -0
  233. aurelian/mcp/mcp_test.py +31 -0
  234. aurelian/mcp/phenopackets_mcp_test.py +103 -0
  235. aurelian/tools/__init__.py +0 -0
  236. aurelian/tools/web/__init__.py +0 -0
  237. aurelian/tools/web/url_download.py +51 -0
  238. aurelian/utils/__init__.py +0 -0
  239. aurelian/utils/async_utils.py +15 -0
  240. aurelian/utils/data_utils.py +32 -0
  241. aurelian/utils/documentation_manager.py +59 -0
  242. aurelian/utils/doi_fetcher.py +238 -0
  243. aurelian/utils/ontology_utils.py +68 -0
  244. aurelian/utils/pdf_fetcher.py +23 -0
  245. aurelian/utils/process_logs.py +100 -0
  246. aurelian/utils/pubmed_utils.py +238 -0
  247. aurelian/utils/pytest_report_to_markdown.py +67 -0
  248. aurelian/utils/robot_ontology_utils.py +112 -0
  249. aurelian/utils/search_utils.py +95 -0
  250. aurelian-0.3.2.dist-info/LICENSE +22 -0
  251. aurelian-0.3.2.dist-info/METADATA +105 -0
  252. aurelian-0.3.2.dist-info/RECORD +254 -0
  253. aurelian-0.3.2.dist-info/WHEEL +4 -0
  254. aurelian-0.3.2.dist-info/entry_points.txt +3 -0
@@ -0,0 +1,87 @@
1
+ #!/usr/bin/env python
2
+ """
3
+ Script for discovering and testing MCP implementations.
4
+ """
5
+
6
+ import argparse
7
+ import importlib
8
+ import inspect
9
+ import sys
10
+ from pathlib import Path
11
+ from typing import List, Optional
12
+
13
+
14
+ def list_mcp_tools(module_path: str) -> List[str]:
15
+ """
16
+ List all MCP tools in a given module.
17
+
18
+ Args:
19
+ module_path: Dot-separated path to the module
20
+
21
+ Returns:
22
+ List of tool names
23
+ """
24
+ try:
25
+ # Import the module
26
+ module = importlib.import_module(module_path)
27
+
28
+ # Find the MCP server instance
29
+ mcp = getattr(module, "mcp", None)
30
+ if not mcp:
31
+ print(f"No 'mcp' instance found in {module_path}")
32
+ return []
33
+
34
+ # Get all functions with the MCP tool decorator
35
+ tools = []
36
+ for name, func in inspect.getmembers(module, inspect.isfunction):
37
+ # Check if this function is an MCP tool
38
+ if hasattr(func, "__mcp_tool__") and func.__mcp_tool__ is True:
39
+ tools.append(name)
40
+
41
+ return tools
42
+ except Exception as e:
43
+ print(f"Error importing {module_path}: {e}")
44
+ return []
45
+
46
+
47
+ def main():
48
+ """Command line interface."""
49
+ parser = argparse.ArgumentParser(description="Discover and test MCP implementations")
50
+ parser.add_argument("--agent", "-a", help="Agent type to test (e.g., 'diagnosis')")
51
+ parser.add_argument("--list", "-l", action="store_true", help="List available MCP modules")
52
+ args = parser.parse_args()
53
+
54
+ # Base path for agent modules
55
+ base_path = Path(__file__).parent.parent
56
+
57
+ if args.list:
58
+ # List all MCP modules
59
+ agents_dir = base_path / "agents"
60
+ print("Available MCP modules:")
61
+ for agent_dir in agents_dir.iterdir():
62
+ if agent_dir.is_dir():
63
+ mcp_file = agent_dir / f"{agent_dir.name}_mcp.py"
64
+ if mcp_file.exists():
65
+ print(f" - {agent_dir.name}")
66
+ return
67
+
68
+ if not args.agent:
69
+ print("Error: Please specify an agent type with --agent")
70
+ sys.exit(1)
71
+
72
+ # Check if the MCP module exists
73
+ agent_type = args.agent
74
+ module_path = f"aurelian.agents.{agent_type}.{agent_type}_mcp"
75
+
76
+ # List the tools
77
+ tools = list_mcp_tools(module_path)
78
+ if tools:
79
+ print(f"MCP tools for {agent_type}:")
80
+ for tool in tools:
81
+ print(f" - {tool}")
82
+ else:
83
+ print(f"No MCP tools found for {agent_type}")
84
+
85
+
86
+ if __name__ == "__main__":
87
+ main()
@@ -0,0 +1,31 @@
1
+ """
2
+ MCP tools for creating LinkML schemas and example datasets
3
+ """
4
+ import sys
5
+
6
+ from mcp.server.fastmcp import FastMCP
7
+
8
+
9
+ # Initialize FastMCP server
10
+ mcp = FastMCP("test")
11
+
12
+ @mcp.tool()
13
+ async def add_two_numbers(n1: int, n2: int) -> int:
14
+ """
15
+ Add two numbers together.
16
+
17
+ Args:
18
+ n1: first number
19
+ n2: second number
20
+
21
+ Returns:
22
+ sum of the two numbers
23
+ """
24
+ return n1 + n2
25
+
26
+
27
+ if __name__ == "__main__":
28
+ print("Running the LinkML MCP tools")
29
+ print("Use Ctrl-C to exit", file=sys.stderr)
30
+ # Initialize and run the server
31
+ mcp.run(transport='stdio')
@@ -0,0 +1,103 @@
1
+ """
2
+ Test for phenopackets MCP functionality
3
+ """
4
+ import os
5
+ import tempfile
6
+ from typing import List, Optional, Dict
7
+
8
+ # try to import, don't die if import fails
9
+ try:
10
+ from mcp import Client
11
+ except ImportError:
12
+ print("mcp package not found. Please install it to run this test.")
13
+ Client = None
14
+ from pydantic import BaseModel
15
+
16
+
17
+ class Message(BaseModel):
18
+ role: str
19
+ content: str
20
+
21
+
22
+ async def test_phenopackets_mcp():
23
+ """Test the phenopackets MCP agent."""
24
+ client = Client("/tmp/phenopackets-mcp", exec_args=["python", "-m", "aurelian.agents.phenopackets.phenopackets_mcp"])
25
+
26
+ import time
27
+ time.sleep(1) # Give the server time to start
28
+
29
+ # Set up a temporary working directory for the test
30
+ tempdir = os.path.join(tempfile.gettempdir(), "test_phenopackets")
31
+ os.makedirs(tempdir, exist_ok=True)
32
+ os.environ["AURELIAN_WORKDIR"] = tempdir
33
+
34
+ # For testing without a real database - would normally come from configuration
35
+ os.environ["PHENOPACKETS_DB_PATH"] = "mongodb://localhost:27017/test_phenopackets"
36
+ os.environ["PHENOPACKETS_DB_NAME"] = "test_phenopackets"
37
+ os.environ["PHENOPACKETS_COLLECTION_NAME"] = "test_main"
38
+
39
+ convo: List[Message] = []
40
+
41
+ def add_to_convo(role: str, content: str) -> Message:
42
+ msg = Message(role=role, content=content)
43
+ convo.append(msg)
44
+ return msg
45
+
46
+ # Make a query
47
+ add_to_convo("user", "What tools are available for working with phenopackets?")
48
+
49
+ message = convo[-1].content
50
+
51
+ response = await client.chat(messages=message)
52
+ print(f"Got response: {response}")
53
+
54
+ # Get available tools
55
+ tool_choices = await client.get_tool_choice(messages=convo)
56
+ print(f"Available tools: {[t['id'] for t in tool_choices]}")
57
+
58
+ # Test the list_files tool
59
+ list_files_tool = next((t for t in tool_choices if t["id"] == "list_files"), None)
60
+ if list_files_tool:
61
+ print(f"Testing list_files tool...")
62
+ tool_input_schema = await client.get_tool_input_schema(tool_id=list_files_tool["id"])
63
+ tool_result = await client.execute_tool(tool_id=list_files_tool["id"], tool_input='{}')
64
+ print(f"list_files result: {tool_result}")
65
+
66
+ # Create a test file
67
+ write_file_tool = next((t for t in tool_choices if t["id"] == "write_to_file"), None)
68
+ if write_file_tool:
69
+ print(f"Testing write_to_file tool...")
70
+ tool_input = '{"file_name": "test.txt", "data": "This is a test file for phenopackets MCP"}'
71
+ tool_result = await client.execute_tool(tool_id=write_file_tool["id"], tool_input=tool_input)
72
+ print(f"write_to_file result: {tool_result}")
73
+
74
+ # Check if the file was created
75
+ if list_files_tool:
76
+ tool_result = await client.execute_tool(tool_id=list_files_tool["id"], tool_input='{}')
77
+ print(f"list_files after writing: {tool_result}")
78
+
79
+ # Read the file
80
+ inspect_file_tool = next((t for t in tool_choices if t["id"] == "inspect_file"), None)
81
+ if inspect_file_tool:
82
+ print(f"Testing inspect_file tool...")
83
+ tool_input = '{"data_file": "test.txt"}'
84
+ tool_result = await client.execute_tool(tool_id=inspect_file_tool["id"], tool_input=tool_input)
85
+ print(f"inspect_file result: {tool_result}")
86
+
87
+ # Try a web search
88
+ search_web_tool = next((t for t in tool_choices if t["id"] == "search_web"), None)
89
+ if search_web_tool:
90
+ print(f"Testing search_web tool...")
91
+ tool_input = '{"query": "phenopackets specification"}'
92
+ try:
93
+ tool_result = await client.execute_tool(tool_id=search_web_tool["id"], tool_input=tool_input)
94
+ print(f"search_web result: {tool_result[:200]}...") # Just show first 200 chars
95
+ except Exception as e:
96
+ print(f"search_web failed (expected in test): {e}")
97
+
98
+ await client.close()
99
+
100
+
101
+ if __name__ == "__main__":
102
+ import asyncio
103
+ asyncio.run(test_phenopackets_mcp())
File without changes
File without changes
@@ -0,0 +1,51 @@
1
+ from dataclasses import dataclass
2
+
3
+ from pydantic import TypeAdapter
4
+ from pydantic_ai.tools import Tool
5
+ from typing_extensions import TypedDict
6
+
7
+ from aurelian.dependencies.workdir import WorkDir
8
+
9
+
10
+ __all__ = ["download_url_tool"]
11
+
12
+ class URLDownloadResult(TypedDict):
13
+ """The result of downloading a URL."""
14
+
15
+ size: int
16
+ """The size of the downloaded content."""
17
+ location: str
18
+ """The location of the downloaded content."""
19
+
20
+ ta = TypeAdapter(list[URLDownloadResult])
21
+
22
+ @dataclass
23
+ class URLDownloadTool:
24
+ """A tool for downloading a URL."""
25
+ workdir: WorkDir
26
+
27
+ async def __call__(self, url:str, local_file_name: str) -> list[dict]:
28
+ """Download the contents of a URL.
29
+
30
+ Args:
31
+ url: URL of the web page
32
+ local_file_name: Name of the local file to save
33
+
34
+ Returns:
35
+ The contents of the web page.
36
+ """
37
+ import asyncio
38
+ import aurelian.utils.search_utils as su
39
+ data = await asyncio.to_thread(su.retrieve_web_page, url)
40
+ self.workdir.write_file(local_file_name, data)
41
+ return ta.validate_python([{"location": local_file_name, "size": len(data)}])
42
+
43
+ def download_url_tool(workdir: WorkDir):
44
+ """Create a URL download tool."""
45
+ udt = URLDownloadTool(workdir=workdir)
46
+ c = udt.__call__
47
+ return Tool(
48
+ udt.__call__,
49
+ name="download_web_page",
50
+ description="Download the contents of a URL.",
51
+ )
File without changes
@@ -0,0 +1,15 @@
1
+ import asyncio
2
+ from typing import Callable
3
+
4
+
5
+ def run_sync(f: Callable):
6
+ loop = asyncio.new_event_loop()
7
+ asyncio.set_event_loop(loop)
8
+ result = f()
9
+
10
+ # Ensure it's a coroutine before running it
11
+ if asyncio.iscoroutine(result):
12
+ result = loop.run_until_complete(result)
13
+
14
+ loop.close()
15
+ return result
@@ -0,0 +1,32 @@
1
+ from typing import Dict, List, Optional, Union
2
+
3
+ from linkml_runtime.dumpers import json_dumper
4
+ from linkml_runtime.utils.yamlutils import YAMLRoot
5
+ from pydantic import BaseModel
6
+
7
+
8
+ def flatten(d: Dict, preserve_keys: Optional[List] = None) -> Dict:
9
+ """Flatten a dictionary"""
10
+ out = {}
11
+ for k, v in d.items():
12
+ if isinstance(v, list):
13
+ if preserve_keys and k in preserve_keys:
14
+ out[k] = [flatten(x, preserve_keys=preserve_keys) for x in v]
15
+ else:
16
+ out[f"{k}_count"] = len(v)
17
+ elif isinstance(v, dict):
18
+ out[k] = flatten(v, preserve_keys=preserve_keys)
19
+ else:
20
+ out[k] = v
21
+ return out
22
+
23
+
24
+ def obj_to_dict(obj: Union[object, YAMLRoot, BaseModel, Dict]) -> Dict:
25
+ if isinstance(obj, YAMLRoot):
26
+ return json_dumper.to_dict(obj)
27
+ elif isinstance(obj, BaseModel):
28
+ return obj.model_dump()
29
+ elif isinstance(obj, dict):
30
+ return obj
31
+ else:
32
+ raise ValueError(f"Cannot convert object of type {type(obj)} to dict")
@@ -0,0 +1,59 @@
1
+ from pydantic import BaseModel
2
+ from pathlib import Path
3
+ from typing import Dict, Optional, List
4
+ from dataclasses import dataclass
5
+
6
+ class Document(BaseModel):
7
+ """
8
+ A document is a file in the documentation directory.
9
+ """
10
+ id: str
11
+ title: str
12
+ path: str
13
+ metadata: Optional[Dict] = None
14
+
15
+ @dataclass
16
+ class DocumentationManager:
17
+ """
18
+ A manager for the documentation directory.
19
+ """
20
+
21
+ documents_dir: Path
22
+ collection_name: Optional[str] = None
23
+
24
+ def all_documents(self) -> List[Document]:
25
+ """
26
+ Get all available documents.
27
+ """
28
+ return [Document(
29
+ id=file_path.stem,
30
+ title=file_path.stem.replace("_", " "),
31
+ path=str(file_path),
32
+ metadata=None
33
+ ) for file_path in self.documents_dir.glob("*.md")]
34
+
35
+ def get_documents_for_prompt(self, extra_text: str = "") -> str:
36
+ """
37
+ Get the documents for the system prompt.
38
+
39
+ Returns:
40
+ A string containing the list of available GO annotation best practice documents
41
+ """
42
+ docs = self.all_documents()
43
+ if not docs:
44
+ raise AssertionError("No best practice documents are available")
45
+
46
+ docs_text = "\n\nThe following documents are available:\n"
47
+ docs_text += "\n".join([f"- {d.title}" for d in docs])
48
+ docs_text += "\n\n" + extra_text
49
+ return docs_text
50
+
51
+ def fetch_document(self, id_or_title: str) -> Document:
52
+ """
53
+ Fetch a document by its ID or title.
54
+ """
55
+ for document in self.all_documents():
56
+ if document.id == id_or_title or document.title == id_or_title:
57
+ return document
58
+ raise KeyError(f"Document with ID or title '{id_or_title}' not found")
59
+
@@ -0,0 +1,238 @@
1
+ import os
2
+ import re
3
+ from tempfile import NamedTemporaryFile
4
+ from typing import Any, Dict, List, Optional
5
+
6
+ import logfire
7
+ import requests
8
+ import requests_cache
9
+ from bs4 import BeautifulSoup
10
+ from markitdown import MarkItDown
11
+ from openai import BaseModel
12
+ from pydantic import Field
13
+
14
+
15
+ class FullTextInfo(BaseModel):
16
+ """Data model for full text information."""
17
+
18
+ success: bool = True
19
+ abstract: Optional[str] = Field(None, description="Abstract of the article")
20
+ text: Optional[str] = Field(None, description="Full text of the article")
21
+ source: Optional[str] = Field(None, description="Source of the full text")
22
+ metadata: Optional[Dict[str, Any]] = Field(None, description="Metadata of the article")
23
+ pdf_url: Optional[str] = Field(None, description="URL to the PDF version of the article")
24
+
25
+
26
+ class DOIFetcher:
27
+ """Fetch metadata and full text for a DOI using various APIs."""
28
+
29
+ def __init__(self, email: Optional[str] = None, url_prefixes: Optional[List[str]] = None):
30
+ """Initialize the DOI fetcher with a contact email (required by some APIs).
31
+
32
+ Args:
33
+ email (str): Contact email for API access
34
+ url_prefixes (List[str]): List of URL prefixes to check for full text
35
+
36
+ """
37
+ self.email = email or os.getenv("EMAIL") or "test@example.com"
38
+ self.url_prefixes = url_prefixes or os.getenv("DOI_FULL_TEXT_URLS", "").split(",")
39
+ self.headers = {"User-Agent": f"DOIFetcher/1.0 (mailto:{email})", "Accept": "application/json"}
40
+
41
+ def clean_text(self, text: str) -> str:
42
+ """Clean extracted text by removing extra whitespace and normalized characters.
43
+
44
+ Args:
45
+ text:
46
+
47
+ Returns:
48
+ str: The cleaned text
49
+
50
+ """
51
+ # Remove extra whitespace
52
+ text = re.sub(r"\s+", " ", text)
53
+ # Remove non-printable characters
54
+ text = "".join(char for char in text if char.isprintable())
55
+ return text.strip()
56
+
57
+ def get_metadata(self, doi: str, strict=False) -> Optional[Dict[str, Any]]:
58
+ """Fetch metadata for a DOI using the Crossref API.
59
+
60
+ Args:
61
+ doi (str): The DOI to look up
62
+ strict (bool): Raise exceptions if API call fails
63
+
64
+ Returns:
65
+ Optional[Dict[str, Any]]: Metadata dictionary if successful, None otherwise
66
+
67
+ """
68
+ base_url = "https://api.crossref.org/works/"
69
+ try:
70
+ response = requests.get(f"{base_url}{doi}", headers=self.headers)
71
+ response.raise_for_status()
72
+ return response.json()["message"]
73
+ except Exception as e:
74
+ if strict:
75
+ raise e
76
+ logfire.warn(f"Error fetching metadata: {e}")
77
+ return None
78
+
79
+ def get_unpaywall_info(self, doi: str, strict=False) -> Optional[Dict[str, Any]]:
80
+ """Check Unpaywall for open access versions.
81
+
82
+ Example:
83
+ >>> fetcher = DOIFetcher()
84
+ >>> doi = "10.1038/nature12373"
85
+ >>> unpaywall_data = fetcher.get_unpaywall_info(doi)
86
+ >>> assert unpaywall_data["doi"] == doi
87
+ >>> unpaywall_data["best_oa_location"]["url_for_pdf"]
88
+ 'https://europepmc.org/articles/pmc4221854?pdf=render'
89
+
90
+ Args:
91
+ doi (str): The DOI to look up
92
+ strict (bool): Raise exceptions if API call fails
93
+
94
+ Returns:
95
+ Optional[Dict[str, Any]]: Unpaywall data if successful, None otherwise
96
+
97
+ """
98
+ base_url = f"https://api.unpaywall.org/v2/{doi}"
99
+ try:
100
+ response = requests.get(f"{base_url}?email={self.email}")
101
+ response.raise_for_status()
102
+ return response.json()
103
+ except Exception as e:
104
+ if strict:
105
+ raise e
106
+ logfire.warn(f"Error fetching Unpaywall data: {e}")
107
+ return None
108
+
109
+ def get_full_text(self, doi: str, fallback_to_abstract=True) -> Optional[str]:
110
+ """Get the full text of a paper using various methods.
111
+
112
+ Example:
113
+ >>> fetcher = DOIFetcher()
114
+ >>> doi = "10.1128/msystems.00045-18"
115
+ >>> full_text = fetcher.get_full_text(doi)
116
+ >>> assert "Populus Microbiome" in full_text
117
+
118
+ Args:
119
+ doi:
120
+ fallback_to_abstract:
121
+
122
+ Returns:
123
+ str: The full text if available, else abstract text if fallback_to_abstract,
124
+ else None
125
+
126
+ """
127
+ info = self.get_full_text_info(doi)
128
+ if not info:
129
+ return None
130
+ text = info.text
131
+ if text:
132
+ return self.clean_text(text)
133
+ if info.pdf_url:
134
+ text = self.text_from_pdf_url(info.pdf_url)
135
+ if text:
136
+ return self.clean_text(text)
137
+ message = "FULL TEXT NOT AVAILABLE"
138
+ if fallback_to_abstract:
139
+ metadata = info.metadata or {}
140
+ abstract = metadata.get("abstract")
141
+ if abstract:
142
+ return self.clean_text(abstract) + f"\n\n{message}"
143
+ return message
144
+
145
+ def get_full_text_info(self, doi: str) -> Optional[FullTextInfo]:
146
+ """Attempt to get the full text of a paper using various methods.
147
+
148
+ >>> fetcher = DOIFetcher()
149
+ >>> doi = "10.1128/msystems.00045-18"
150
+ >>> info = fetcher.get_full_text_info(doi)
151
+ >>> metadata = info.metadata
152
+ >>> metadata["type"]
153
+ 'journal-article'
154
+ >>> metadata["title"][0][0:20]
155
+ 'Exploration of the B'
156
+ >>> assert info.pdf_url is not None
157
+ >>> info.pdf_url
158
+ 'https://europepmc.org/articles/pmc6172771?pdf=render'
159
+
160
+ Args:
161
+ doi (str): The DOI to fetch
162
+
163
+ Returns:
164
+ FullTextInfo: Full text information
165
+
166
+ """
167
+ # Get metadata
168
+ metadata = self.get_metadata(doi)
169
+
170
+ # Check Unpaywall
171
+ unpaywall_data = self.get_unpaywall_info(doi)
172
+ if unpaywall_data and unpaywall_data.get("is_oa"):
173
+ locations = unpaywall_data.get("oa_locations", [])
174
+ if unpaywall_data.get("best_oa_location"):
175
+ best_oa_location = unpaywall_data.get("best_oa_location")
176
+ locations = [best_oa_location] + locations
177
+
178
+ # Find best open access location
179
+ for location in locations:
180
+ pdf_url = location.get("url_for_pdf")
181
+ if pdf_url:
182
+ return FullTextInfo(text=None, pdf_url=pdf_url, source="unpaywall", metadata=metadata)
183
+
184
+ # Fallback
185
+ url_prefixes = os.getenv("DOI_FULL_TEXT_URLS", "").split(",")
186
+
187
+ for url_prefix in url_prefixes:
188
+ url_prefix.rstrip("/")
189
+ url = f"{url_prefix}/{doi}"
190
+ try:
191
+ response = requests.get(url)
192
+ if response.status_code == 200:
193
+ soup = BeautifulSoup(response.text, "html.parser")
194
+ pdf_embed = soup.find("embed", id="pdf")
195
+ if pdf_embed and pdf_embed.get("src"):
196
+ pdf_url = pdf_embed["src"]
197
+ # Remove any URL parameters after #
198
+ pdf_url = pdf_url.split("#")[0]
199
+ if not pdf_url.startswith("http"):
200
+ pdf_url = "https:" + pdf_url
201
+ return FullTextInfo(
202
+ pdf_url=pdf_url,
203
+ source=url,
204
+ metadata=metadata,
205
+ )
206
+ except Exception:
207
+ continue
208
+
209
+ def text_from_pdf_url(self, pdf_url: str, raise_for_status=False) -> Optional[str]:
210
+ """Extract text from a PDF URL.
211
+
212
+ Example:
213
+ >>> fetcher = DOIFetcher()
214
+ >>> pdf_url = "https://ceur-ws.org/Vol-1747/IT201_ICBO2016.pdf"
215
+ >>> text = fetcher.text_from_pdf_url(pdf_url)
216
+ >>> assert "biosphere" in text
217
+
218
+ Args:
219
+ pdf_url:
220
+ raise_for_status:
221
+
222
+ Returns:
223
+
224
+ """
225
+ session = requests_cache.CachedSession("pdf_cache")
226
+ # Download the PDF
227
+ response = session.get(pdf_url)
228
+ if raise_for_status:
229
+ response.raise_for_status()
230
+ if response.status_code != 200:
231
+ return None
232
+ with NamedTemporaryFile(delete=False) as tmpf:
233
+ tmpf.write(response.content)
234
+ tmp_name = tmpf.name
235
+ with open(tmp_name, "wb") as f:
236
+ f.write(response.content)
237
+ md = MarkItDown()
238
+ return md.convert(tmpf.name).text_content