Skip to content

Commit c0d3bd2

Browse files
committed
Initial commit
0 parents  commit c0d3bd2

11 files changed

Lines changed: 1533 additions & 0 deletions

File tree

.gitignore

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# Python
2+
__pycache__/
3+
*.py[cod]
4+
*$py.class
5+
*.so
6+
.Python
7+
build/
8+
develop-eggs/
9+
dist/
10+
downloads/
11+
eggs/
12+
.eggs/
13+
lib/
14+
lib64/
15+
parts/
16+
sdist/
17+
var/
18+
wheels/
19+
*.egg-info/
20+
.installed.cfg
21+
*.egg
22+
23+
# Unit test / coverage reports
24+
htmlcov/
25+
.tox/
26+
.coverage
27+
.coverage.*
28+
.cache
29+
nosetests.xml
30+
coverage.xml
31+
*.cover
32+
.hypothesis/
33+
34+
# Environments
35+
.env
36+
.venv
37+
env/
38+
venv/
39+
ENV/
40+
env.bak/
41+
venv.bak/
42+
43+
# Editor settings
44+
.idea/
45+
.vscode/
46+
*.swp
47+
*.swo
48+
.claude

README.md

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# ols-mcp
2+
3+
MCP for retrieving things from the Ontology Lookup Service
4+
5+
## Installation
6+
7+
You can install the package from source:
8+
9+
```bash
10+
pip install -e .
11+
```
12+
13+
Or using uv:
14+
15+
```bash
16+
uv pip install -e .
17+
```
18+
19+
## Usage
20+
21+
You can use the CLI:
22+
23+
```bash
24+
ols_mcp
25+
```
26+
27+
Or import in your Python code:
28+
29+
```python
30+
from ols_mcp.main import create_mcp
31+
32+
mcp = create_mcp()
33+
mcp.run()
34+
```
35+
36+
## Development
37+
38+
### Local Setup
39+
40+
```bash
41+
# Clone the repository
42+
git clone https://github.com/justaddcoffee/ols-mcp.git
43+
cd ols-mcp
44+
45+
# Install development dependencies
46+
uv pip install -e ".[dev]"
47+
```
48+
49+
50+
## License
51+
52+
BSD-3-Clause

pyproject.toml

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
[build-system]
2+
requires = ["hatchling"]
3+
build-backend = "hatchling.build"
4+
5+
[project]
6+
name = "ols-mcp"
7+
version = "0.1.0"
8+
description = "MCP for retrieving things from the Ontology Lookup Service"
9+
readme = "README.md"
10+
authors = [
11+
{name = "Justin Reese", email = "justinreese@lbl.gov"},
12+
]
13+
requires-python = ">=3.11"
14+
dependencies = [
15+
"fastmcp>=2.7.1",
16+
"requests>=2.32.4",
17+
]
18+
19+
[dependency-groups]
20+
dev = [
21+
"pytest",
22+
"black",
23+
]
24+
25+
[project.urls]
26+
"Homepage" = "https://github.com/justaddcoffee/ols-mcp"
27+
"Bug Tracker" = "https://github.com/justaddcoffee/ols-mcp/issues"
28+
29+
[project.scripts]
30+
ols-mcp = "ols_mcp.main:main"
31+
32+
[tool.hatch.build.targets.wheel]
33+
packages = ["src/ols_mcp"]
34+
35+
[tool.pytest.ini_options]
36+
testpaths = ["tests"]

src/ols_mcp/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
"""ols-mcp package for querying database_name API."""
2+
3+
__version__ = "0.1.0"

src/ols_mcp/__main__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
from .main import main
2+
3+
if __name__ == "__main__":
4+
main()

src/ols_mcp/api.py

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
################################################################################
2+
# ols_mcp/api.py
3+
# This module contains wrapper functions that interact with the OLS API endpoints
4+
################################################################################
5+
import json
6+
from typing import Any, Dict, List, Optional, Union
7+
import requests
8+
9+
10+
def search_ontologies(
11+
query: str,
12+
ontologies: Optional[List[str]] = None,
13+
max_results: int = 20,
14+
exact: bool = False,
15+
verbose: bool = False,
16+
) -> List[Dict[str, Any]]:
17+
"""
18+
Search across all ontologies in the OLS.
19+
20+
Args:
21+
query: The search term
22+
ontologies: List of specific ontology IDs to search within (optional)
23+
max_results: Maximum number of results to return
24+
exact: Whether to perform exact matching
25+
verbose: If True, print progress information during retrieval
26+
27+
Returns:
28+
A list of dictionaries, where each dictionary represents a search result.
29+
"""
30+
base_url = "https://www.ebi.ac.uk/ols/api/search"
31+
32+
params = {
33+
"q": query,
34+
"rows": max_results,
35+
"exact": exact
36+
}
37+
38+
if ontologies:
39+
params["ontology"] = ",".join(ontologies)
40+
41+
if verbose:
42+
print(f"Searching OLS for: {query}")
43+
44+
response = requests.get(base_url, params=params)
45+
response.raise_for_status()
46+
data = response.json()
47+
48+
# Extract the docs from the response
49+
results = data.get("response", {}).get("docs", [])
50+
51+
if verbose:
52+
print(f"Found {len(results)} results")
53+
54+
return results
55+
56+
57+
def get_ontology_details(ontology_id: str, verbose: bool = False) -> Dict[str, Any]:
58+
"""
59+
Get details about a specific ontology.
60+
61+
Args:
62+
ontology_id: The ID of the ontology (e.g., 'go', 'uberon')
63+
verbose: If True, print progress information
64+
65+
Returns:
66+
A dictionary containing ontology details.
67+
"""
68+
base_url = f"https://www.ebi.ac.uk/ols/api/ontologies/{ontology_id}"
69+
70+
if verbose:
71+
print(f"Fetching details for ontology: {ontology_id}")
72+
73+
response = requests.get(base_url)
74+
response.raise_for_status()
75+
data = response.json()
76+
77+
if verbose:
78+
print(f"Retrieved details for {ontology_id}")
79+
80+
return data
81+
82+
83+
def get_ontology_terms(
84+
ontology_id: str,
85+
max_results: int = 20,
86+
page_size: int = 20,
87+
iri: Optional[str] = None,
88+
short_form: Optional[str] = None,
89+
obo_id: Optional[str] = None,
90+
verbose: bool = False,
91+
) -> List[Dict[str, Any]]:
92+
"""
93+
Get classes/terms from a specific ontology.
94+
95+
Args:
96+
ontology_id: The ID of the ontology (e.g., 'go', 'uberon')
97+
max_results: Maximum number of results to return
98+
page_size: Number of results per page
99+
iri: Filter by specific IRI
100+
short_form: Filter by short form
101+
obo_id: Filter by OBO ID
102+
verbose: If True, print progress information
103+
104+
Returns:
105+
A list of dictionaries, where each dictionary represents a term.
106+
"""
107+
base_url = f"https://www.ebi.ac.uk/ols/api/ontologies/{ontology_id}/terms"
108+
109+
params = {"size": min(page_size, max_results)}
110+
111+
if iri:
112+
params["iri"] = iri
113+
if short_form:
114+
params["short_form"] = short_form
115+
if obo_id:
116+
params["obo_id"] = obo_id
117+
118+
all_terms = []
119+
page = 0
120+
121+
if verbose:
122+
print(f"Fetching terms from ontology: {ontology_id}")
123+
124+
while len(all_terms) < max_results:
125+
params["page"] = page
126+
127+
response = requests.get(base_url, params=params)
128+
response.raise_for_status()
129+
data = response.json()
130+
131+
terms = data.get("_embedded", {}).get("terms", [])
132+
if not terms:
133+
break
134+
135+
all_terms.extend(terms)
136+
137+
if verbose:
138+
print(f"Fetched page {page + 1}, total terms so far: {len(all_terms)}")
139+
140+
# Check if we have more pages
141+
if not data.get("page", {}).get("number", 0) < data.get("page", {}).get("totalPages", 0) - 1:
142+
break
143+
144+
page += 1
145+
146+
# Truncate to max_results
147+
result = all_terms[:max_results]
148+
149+
if verbose:
150+
print(f"Retrieved {len(result)} terms from {ontology_id}")
151+
152+
return result

src/ols_mcp/main.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
################################################################################
2+
# ols_mcp/main.py
3+
# This module sets up the FastMCP CLI interface
4+
################################################################################
5+
6+
from fastmcp import FastMCP
7+
from ols_mcp.tools import (
8+
search_all_ontologies,
9+
get_ontology_info,
10+
get_terms_from_ontology
11+
)
12+
13+
14+
# Create the FastMCP instance at module level
15+
mcp = FastMCP("ols_mcp")
16+
17+
# Register all tools
18+
mcp.tool(search_all_ontologies)
19+
mcp.tool(get_ontology_info)
20+
mcp.tool(get_terms_from_ontology)
21+
22+
23+
def main():
24+
"""Main entry point for the application."""
25+
mcp.run()
26+
27+
28+
if __name__ == "__main__":
29+
main()

0 commit comments

Comments
 (0)