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.
- aurelian/__init__.py +9 -0
- aurelian/agents/__init__.py +0 -0
- aurelian/agents/amigo/__init__.py +3 -0
- aurelian/agents/amigo/amigo_agent.py +77 -0
- aurelian/agents/amigo/amigo_config.py +85 -0
- aurelian/agents/amigo/amigo_evals.py +73 -0
- aurelian/agents/amigo/amigo_gradio.py +52 -0
- aurelian/agents/amigo/amigo_mcp.py +152 -0
- aurelian/agents/amigo/amigo_tools.py +152 -0
- aurelian/agents/biblio/__init__.py +42 -0
- aurelian/agents/biblio/biblio_agent.py +94 -0
- aurelian/agents/biblio/biblio_config.py +40 -0
- aurelian/agents/biblio/biblio_gradio.py +67 -0
- aurelian/agents/biblio/biblio_mcp.py +115 -0
- aurelian/agents/biblio/biblio_tools.py +164 -0
- aurelian/agents/biblio_agent.py +46 -0
- aurelian/agents/checklist/__init__.py +44 -0
- aurelian/agents/checklist/checklist_agent.py +85 -0
- aurelian/agents/checklist/checklist_config.py +28 -0
- aurelian/agents/checklist/checklist_gradio.py +70 -0
- aurelian/agents/checklist/checklist_mcp.py +86 -0
- aurelian/agents/checklist/checklist_tools.py +141 -0
- aurelian/agents/checklist/content/checklists.yaml +7 -0
- aurelian/agents/checklist/content/streams.csv +136 -0
- aurelian/agents/checklist_agent.py +40 -0
- aurelian/agents/chemistry/__init__.py +3 -0
- aurelian/agents/chemistry/chemistry_agent.py +46 -0
- aurelian/agents/chemistry/chemistry_config.py +71 -0
- aurelian/agents/chemistry/chemistry_evals.py +79 -0
- aurelian/agents/chemistry/chemistry_gradio.py +50 -0
- aurelian/agents/chemistry/chemistry_mcp.py +120 -0
- aurelian/agents/chemistry/chemistry_tools.py +121 -0
- aurelian/agents/chemistry/image_agent.py +15 -0
- aurelian/agents/d4d/__init__.py +30 -0
- aurelian/agents/d4d/d4d_agent.py +72 -0
- aurelian/agents/d4d/d4d_config.py +46 -0
- aurelian/agents/d4d/d4d_gradio.py +58 -0
- aurelian/agents/d4d/d4d_mcp.py +71 -0
- aurelian/agents/d4d/d4d_tools.py +157 -0
- aurelian/agents/d4d_agent.py +64 -0
- aurelian/agents/diagnosis/__init__.py +33 -0
- aurelian/agents/diagnosis/diagnosis_agent.py +53 -0
- aurelian/agents/diagnosis/diagnosis_config.py +48 -0
- aurelian/agents/diagnosis/diagnosis_evals.py +76 -0
- aurelian/agents/diagnosis/diagnosis_gradio.py +52 -0
- aurelian/agents/diagnosis/diagnosis_mcp.py +141 -0
- aurelian/agents/diagnosis/diagnosis_tools.py +204 -0
- aurelian/agents/diagnosis_agent.py +28 -0
- aurelian/agents/draw/__init__.py +3 -0
- aurelian/agents/draw/draw_agent.py +39 -0
- aurelian/agents/draw/draw_config.py +26 -0
- aurelian/agents/draw/draw_gradio.py +50 -0
- aurelian/agents/draw/draw_mcp.py +94 -0
- aurelian/agents/draw/draw_tools.py +100 -0
- aurelian/agents/draw/judge_agent.py +18 -0
- aurelian/agents/filesystem/__init__.py +0 -0
- aurelian/agents/filesystem/filesystem_config.py +27 -0
- aurelian/agents/filesystem/filesystem_gradio.py +49 -0
- aurelian/agents/filesystem/filesystem_mcp.py +89 -0
- aurelian/agents/filesystem/filesystem_tools.py +95 -0
- aurelian/agents/filesystem/py.typed +0 -0
- aurelian/agents/github/__init__.py +0 -0
- aurelian/agents/github/github_agent.py +83 -0
- aurelian/agents/github/github_cli.py +248 -0
- aurelian/agents/github/github_config.py +22 -0
- aurelian/agents/github/github_gradio.py +152 -0
- aurelian/agents/github/github_mcp.py +252 -0
- aurelian/agents/github/github_tools.py +408 -0
- aurelian/agents/github/github_tools.py.tmp +413 -0
- aurelian/agents/goann/__init__.py +13 -0
- aurelian/agents/goann/documents/Transcription_Factors_Annotation_Guidelines.md +1000 -0
- aurelian/agents/goann/documents/Transcription_Factors_Annotation_Guidelines.pdf +0 -0
- aurelian/agents/goann/documents/Transcription_Factors_Annotation_Guidelines_Paper.md +693 -0
- aurelian/agents/goann/documents/Transcription_Factors_Annotation_Guidelines_Paper.pdf +0 -0
- aurelian/agents/goann/goann_agent.py +90 -0
- aurelian/agents/goann/goann_config.py +90 -0
- aurelian/agents/goann/goann_evals.py +104 -0
- aurelian/agents/goann/goann_gradio.py +62 -0
- aurelian/agents/goann/goann_mcp.py +0 -0
- aurelian/agents/goann/goann_tools.py +65 -0
- aurelian/agents/gocam/__init__.py +43 -0
- aurelian/agents/gocam/documents/DNA-binding transcription factor activity annotation guidelines.docx +0 -0
- aurelian/agents/gocam/documents/DNA-binding transcription factor activity annotation guidelines.pdf +0 -0
- aurelian/agents/gocam/documents/DNA-binding_transcription_factor_activity_annotation_guidelines.md +100 -0
- aurelian/agents/gocam/documents/E3 ubiquitin ligases.docx +0 -0
- aurelian/agents/gocam/documents/E3 ubiquitin ligases.pdf +0 -0
- aurelian/agents/gocam/documents/E3_ubiquitin_ligases.md +134 -0
- aurelian/agents/gocam/documents/GO-CAM annotation guidelines README.docx +0 -0
- aurelian/agents/gocam/documents/GO-CAM annotation guidelines README.pdf +0 -0
- aurelian/agents/gocam/documents/GO-CAM modelling guidelines TO DO.docx +0 -0
- aurelian/agents/gocam/documents/GO-CAM modelling guidelines TO DO.pdf +0 -0
- aurelian/agents/gocam/documents/GO-CAM_annotation_guidelines_README.md +1 -0
- aurelian/agents/gocam/documents/GO-CAM_modelling_guidelines_TO_DO.md +3 -0
- aurelian/agents/gocam/documents/How to annotate complexes in GO-CAM.docx +0 -0
- aurelian/agents/gocam/documents/How to annotate complexes in GO-CAM.pdf +0 -0
- aurelian/agents/gocam/documents/How to annotate molecular adaptors.docx +0 -0
- aurelian/agents/gocam/documents/How to annotate molecular adaptors.pdf +0 -0
- aurelian/agents/gocam/documents/How to annotate sequestering proteins.docx +0 -0
- aurelian/agents/gocam/documents/How to annotate sequestering proteins.pdf +0 -0
- aurelian/agents/gocam/documents/How_to_annotate_complexes_in_GO-CAM.md +29 -0
- aurelian/agents/gocam/documents/How_to_annotate_molecular_adaptors.md +31 -0
- aurelian/agents/gocam/documents/How_to_annotate_sequestering_proteins.md +42 -0
- aurelian/agents/gocam/documents/Molecular adaptor activity.docx +0 -0
- aurelian/agents/gocam/documents/Molecular adaptor activity.pdf +0 -0
- aurelian/agents/gocam/documents/Molecular carrier activity.docx +0 -0
- aurelian/agents/gocam/documents/Molecular carrier activity.pdf +0 -0
- aurelian/agents/gocam/documents/Molecular_adaptor_activity.md +51 -0
- aurelian/agents/gocam/documents/Molecular_carrier_activity.md +41 -0
- aurelian/agents/gocam/documents/Protein sequestering activity.docx +0 -0
- aurelian/agents/gocam/documents/Protein sequestering activity.pdf +0 -0
- aurelian/agents/gocam/documents/Protein_sequestering_activity.md +50 -0
- aurelian/agents/gocam/documents/Signaling receptor activity annotation guidelines.docx +0 -0
- aurelian/agents/gocam/documents/Signaling receptor activity annotation guidelines.pdf +0 -0
- aurelian/agents/gocam/documents/Signaling_receptor_activity_annotation_guidelines.md +187 -0
- aurelian/agents/gocam/documents/Transcription coregulator activity.docx +0 -0
- aurelian/agents/gocam/documents/Transcription coregulator activity.pdf +0 -0
- aurelian/agents/gocam/documents/Transcription_coregulator_activity.md +36 -0
- aurelian/agents/gocam/documents/Transporter activity annotation annotation guidelines.docx +0 -0
- aurelian/agents/gocam/documents/Transporter activity annotation annotation guidelines.pdf +0 -0
- aurelian/agents/gocam/documents/Transporter_activity_annotation_annotation_guidelines.md +43 -0
- Regulatory Processes in GO-CAM.docx +0 -0
- Regulatory Processes in GO-CAM.pdf +0 -0
- aurelian/agents/gocam/documents/WIP_-_Regulation_and_Regulatory_Processes_in_GO-CAM.md +31 -0
- aurelian/agents/gocam/documents/md/DNA-binding_transcription_factor_activity_annotation_guidelines.md +131 -0
- aurelian/agents/gocam/documents/md/E3_ubiquitin_ligases.md +166 -0
- aurelian/agents/gocam/documents/md/GO-CAM_annotation_guidelines_README.md +1 -0
- aurelian/agents/gocam/documents/md/GO-CAM_modelling_guidelines_TO_DO.md +5 -0
- aurelian/agents/gocam/documents/md/How_to_annotate_complexes_in_GO-CAM.md +28 -0
- aurelian/agents/gocam/documents/md/How_to_annotate_molecular_adaptors.md +19 -0
- aurelian/agents/gocam/documents/md/How_to_annotate_sequestering_proteins.md +38 -0
- aurelian/agents/gocam/documents/md/Molecular_adaptor_activity.md +52 -0
- aurelian/agents/gocam/documents/md/Molecular_carrier_activity.md +59 -0
- aurelian/agents/gocam/documents/md/Protein_sequestering_activity.md +52 -0
- aurelian/agents/gocam/documents/md/Signaling_receptor_activity_annotation_guidelines.md +271 -0
- aurelian/agents/gocam/documents/md/Transcription_coregulator_activity.md +54 -0
- aurelian/agents/gocam/documents/md/Transporter_activity_annotation_annotation_guidelines.md +38 -0
- aurelian/agents/gocam/documents/md/WIP_-_Regulation_and_Regulatory_Processes_in_GO-CAM.md +39 -0
- aurelian/agents/gocam/documents/pandoc_md/Signaling_receptor_activity_annotation_guidelines.md +334 -0
- aurelian/agents/gocam/gocam_agent.py +240 -0
- aurelian/agents/gocam/gocam_config.py +85 -0
- aurelian/agents/gocam/gocam_curator_agent.py +46 -0
- aurelian/agents/gocam/gocam_evals.py +67 -0
- aurelian/agents/gocam/gocam_gradio.py +89 -0
- aurelian/agents/gocam/gocam_mcp.py +224 -0
- aurelian/agents/gocam/gocam_tools.py +294 -0
- aurelian/agents/linkml/__init__.py +0 -0
- aurelian/agents/linkml/linkml_agent.py +62 -0
- aurelian/agents/linkml/linkml_config.py +48 -0
- aurelian/agents/linkml/linkml_evals.py +66 -0
- aurelian/agents/linkml/linkml_gradio.py +45 -0
- aurelian/agents/linkml/linkml_mcp.py +186 -0
- aurelian/agents/linkml/linkml_tools.py +102 -0
- aurelian/agents/literature/__init__.py +3 -0
- aurelian/agents/literature/literature_agent.py +55 -0
- aurelian/agents/literature/literature_config.py +35 -0
- aurelian/agents/literature/literature_gradio.py +52 -0
- aurelian/agents/literature/literature_mcp.py +174 -0
- aurelian/agents/literature/literature_tools.py +182 -0
- aurelian/agents/monarch/__init__.py +25 -0
- aurelian/agents/monarch/monarch_agent.py +44 -0
- aurelian/agents/monarch/monarch_config.py +45 -0
- aurelian/agents/monarch/monarch_gradio.py +51 -0
- aurelian/agents/monarch/monarch_mcp.py +65 -0
- aurelian/agents/monarch/monarch_tools.py +113 -0
- aurelian/agents/oak/__init__.py +0 -0
- aurelian/agents/oak/oak_config.py +27 -0
- aurelian/agents/oak/oak_gradio.py +57 -0
- aurelian/agents/ontology_mapper/__init__.py +31 -0
- aurelian/agents/ontology_mapper/ontology_mapper_agent.py +56 -0
- aurelian/agents/ontology_mapper/ontology_mapper_config.py +50 -0
- aurelian/agents/ontology_mapper/ontology_mapper_evals.py +108 -0
- aurelian/agents/ontology_mapper/ontology_mapper_gradio.py +58 -0
- aurelian/agents/ontology_mapper/ontology_mapper_mcp.py +81 -0
- aurelian/agents/ontology_mapper/ontology_mapper_tools.py +147 -0
- aurelian/agents/phenopackets/__init__.py +3 -0
- aurelian/agents/phenopackets/phenopackets_agent.py +58 -0
- aurelian/agents/phenopackets/phenopackets_config.py +72 -0
- aurelian/agents/phenopackets/phenopackets_evals.py +99 -0
- aurelian/agents/phenopackets/phenopackets_gradio.py +55 -0
- aurelian/agents/phenopackets/phenopackets_mcp.py +178 -0
- aurelian/agents/phenopackets/phenopackets_tools.py +127 -0
- aurelian/agents/rag/__init__.py +40 -0
- aurelian/agents/rag/rag_agent.py +83 -0
- aurelian/agents/rag/rag_config.py +80 -0
- aurelian/agents/rag/rag_gradio.py +67 -0
- aurelian/agents/rag/rag_mcp.py +107 -0
- aurelian/agents/rag/rag_tools.py +189 -0
- aurelian/agents/rag_agent.py +54 -0
- aurelian/agents/robot/__init__.py +0 -0
- aurelian/agents/robot/assets/__init__.py +3 -0
- aurelian/agents/robot/assets/template.md +384 -0
- aurelian/agents/robot/robot_config.py +25 -0
- aurelian/agents/robot/robot_gradio.py +46 -0
- aurelian/agents/robot/robot_mcp.py +100 -0
- aurelian/agents/robot/robot_ontology_agent.py +139 -0
- aurelian/agents/robot/robot_tools.py +50 -0
- aurelian/agents/talisman/__init__.py +3 -0
- aurelian/agents/talisman/talisman_agent.py +126 -0
- aurelian/agents/talisman/talisman_config.py +66 -0
- aurelian/agents/talisman/talisman_gradio.py +50 -0
- aurelian/agents/talisman/talisman_mcp.py +168 -0
- aurelian/agents/talisman/talisman_tools.py +720 -0
- aurelian/agents/ubergraph/__init__.py +40 -0
- aurelian/agents/ubergraph/ubergraph_agent.py +71 -0
- aurelian/agents/ubergraph/ubergraph_config.py +79 -0
- aurelian/agents/ubergraph/ubergraph_gradio.py +48 -0
- aurelian/agents/ubergraph/ubergraph_mcp.py +69 -0
- aurelian/agents/ubergraph/ubergraph_tools.py +118 -0
- aurelian/agents/uniprot/__init__.py +37 -0
- aurelian/agents/uniprot/uniprot_agent.py +43 -0
- aurelian/agents/uniprot/uniprot_config.py +43 -0
- aurelian/agents/uniprot/uniprot_evals.py +99 -0
- aurelian/agents/uniprot/uniprot_gradio.py +48 -0
- aurelian/agents/uniprot/uniprot_mcp.py +168 -0
- aurelian/agents/uniprot/uniprot_tools.py +136 -0
- aurelian/agents/web/__init__.py +0 -0
- aurelian/agents/web/web_config.py +27 -0
- aurelian/agents/web/web_gradio.py +48 -0
- aurelian/agents/web/web_mcp.py +50 -0
- aurelian/agents/web/web_tools.py +108 -0
- aurelian/chat.py +23 -0
- aurelian/cli.py +800 -0
- aurelian/dependencies/__init__.py +0 -0
- aurelian/dependencies/workdir.py +78 -0
- aurelian/mcp/__init__.py +0 -0
- aurelian/mcp/amigo_mcp_test.py +86 -0
- aurelian/mcp/config_generator.py +123 -0
- aurelian/mcp/example_config.json +43 -0
- aurelian/mcp/generate_sample_config.py +37 -0
- aurelian/mcp/gocam_mcp_test.py +126 -0
- aurelian/mcp/linkml_mcp_tools.py +190 -0
- aurelian/mcp/mcp_discovery.py +87 -0
- aurelian/mcp/mcp_test.py +31 -0
- aurelian/mcp/phenopackets_mcp_test.py +103 -0
- aurelian/tools/__init__.py +0 -0
- aurelian/tools/web/__init__.py +0 -0
- aurelian/tools/web/url_download.py +51 -0
- aurelian/utils/__init__.py +0 -0
- aurelian/utils/async_utils.py +15 -0
- aurelian/utils/data_utils.py +32 -0
- aurelian/utils/documentation_manager.py +59 -0
- aurelian/utils/doi_fetcher.py +238 -0
- aurelian/utils/ontology_utils.py +68 -0
- aurelian/utils/pdf_fetcher.py +23 -0
- aurelian/utils/process_logs.py +100 -0
- aurelian/utils/pubmed_utils.py +238 -0
- aurelian/utils/pytest_report_to_markdown.py +67 -0
- aurelian/utils/robot_ontology_utils.py +112 -0
- aurelian/utils/search_utils.py +95 -0
- aurelian-0.3.2.dist-info/LICENSE +22 -0
- aurelian-0.3.2.dist-info/METADATA +105 -0
- aurelian-0.3.2.dist-info/RECORD +254 -0
- aurelian-0.3.2.dist-info/WHEEL +4 -0
- 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()
|
aurelian/mcp/mcp_test.py
ADDED
@@ -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
|