-
Notifications
You must be signed in to change notification settings - Fork 10
add mcp server and docker-compose #187
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| services: | ||
| papers-search-mcp-server: | ||
| build: | ||
| context: .. | ||
| dockerfile: mcp-servers/papers-search-mcp-server/Dockerfile | ||
| container_name: papers-search-mcp-server | ||
| env_file: | ||
| - ./papers-search-mcp-server/.env | ||
| environment: | ||
| PYTHONUNBUFFERED: "1" | ||
| ports: | ||
| - "7331:7331" | ||
| restart: unless-stopped | ||
|
|
||
| chemical-mcp-server: | ||
| build: | ||
| context: .. | ||
| dockerfile: mcp-servers/chemical-mcp-server/Dockerfile | ||
| ports: | ||
| - "7332:7331" | ||
| volumes: | ||
| - ./data:/tmp/chemical_mcp_annotated | ||
| env_file: | ||
| - .env | ||
| restart: unless-stopped |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| OPENALEX_API_KEY='api-key' | ||
| OPENALEX_EMAIL='your-email' | ||
|
|
||
| ENDPOINT_URL='http://0.0.0.0:9000' | ||
| ACCESS_KEY='username' | ||
| SECRET_KEY='password' | ||
| BUCKET_NAME='bucket-name' |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| FROM python:3.11-slim | ||
|
|
||
| WORKDIR /app | ||
|
|
||
| RUN pip install uv | ||
|
|
||
| COPY mcp-servers/papers-search-mcp-server/pyproject.toml mcp-servers/papers-search-mcp-server/uv.lock ./ | ||
|
|
||
| RUN uv sync --frozen --no-install-project | ||
|
|
||
| COPY mcp-servers/papers-search-mcp-server/ ./ | ||
| COPY CoScientist/paper_parser /app/CoScientist/paper_parser | ||
| COPY definitions.py ./ | ||
|
|
||
| CMD ["uv", "run", "--no-project", "python", "papers_search_server.py"] |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,78 @@ | ||
| # papers-search-mcp-server | ||
|
|
||
| ## Environment | ||
|
|
||
| Create a `.env` file in this directory based on `.env.example`. | ||
|
|
||
| ## Server Tools | ||
|
|
||
| The server exposes three MCP tools: | ||
|
|
||
| ### `search_entity` | ||
|
|
||
| Finds an OpenAlex entity ID by name. | ||
|
|
||
| Main arguments: | ||
|
|
||
| - `entity_type`: one of `author`, `source`, `institution` | ||
| - `entity_name`: entity name to search for | ||
|
|
||
| Use this tool when a paper query depends on an author, journal, or institution filter. | ||
|
|
||
| ### `search_papers` | ||
|
|
||
| Searches OpenAlex works and returns paper metadata. | ||
|
|
||
| Main arguments: | ||
|
|
||
| - `keywords`: free-text topic or query string | ||
| - `author_id`: OpenAlex author ID | ||
| - `institution_id`: OpenAlex institution ID | ||
| - `source_id`: OpenAlex source ID | ||
| - `publication_year`: year or year filter such as `2025` or `>2020` | ||
| - `open_access`: whether to keep only open-access papers | ||
| - `has_pdf`: whether to keep only papers with an available PDF | ||
| - `limit`: maximum number of results | ||
| - `sort`: OpenAlex sort field such as `publication_year:desc` or `cited_by_count:desc` | ||
|
|
||
| This tool returns a human-readable summary and normalized metadata for the matching papers. | ||
|
|
||
| ### `download_papers_from_search` | ||
|
|
||
| Searches OpenAlex works and uploads matching PDFs directly to S3. | ||
|
|
||
| Main arguments: | ||
|
|
||
| - All search arguments from `search_papers` | ||
| - `session_id`: session identifier used in the S3 prefix | ||
| - `user_id`: user identifier used in the S3 prefix | ||
|
|
||
| Uploaded files are stored under the S3 prefix `user_id/session_id/web_search_res/`. | ||
|
|
||
| ## Run With uv | ||
|
|
||
| From `ChemCoScientist/mcp/papers_search`: | ||
|
|
||
| ```bash | ||
| set -a | ||
| source .env | ||
| set +a | ||
| uv sync --frozen --no-install-project | ||
| uv run --no-project python papers_search_server.py | ||
| ``` | ||
|
|
||
| ## Run With Docker | ||
|
|
||
| Build from `ChemCoScientist/mcp/papers_search`, but use the repository root as the Docker build context: | ||
|
|
||
| ```bash | ||
| docker build -f Dockerfile -t papers-search-mcp-server ../../.. | ||
| ``` | ||
|
|
||
| Run the container with the environment file and port mapping: | ||
|
|
||
| ```bash | ||
| docker run --rm -i -p 7331:7331 --env-file .env papers-search-mcp-server | ||
| ``` | ||
|
|
||
| The server will be available at `http://localhost:7331/mcp`. |
154 changes: 154 additions & 0 deletions
154
mcp-servers/papers-search-mcp-server/openalex_client.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,154 @@ | ||
| from __future__ import annotations | ||
|
|
||
| import time | ||
| import logging | ||
| import os | ||
| from urllib.parse import urljoin | ||
| import requests | ||
| from dotenv import load_dotenv | ||
| from pprint import pprint | ||
|
|
||
| DOWNLOADED_PAPERS_PATH = os.environ.get("DOWNLOADED_PAPERS_PATH") | ||
| OPENALEX_EMAIL = os.environ.get("OPENALEX_EMAIL") | ||
|
|
||
|
|
||
| class OpenAlexClient: | ||
| """Client for interacting with the OpenAlex API.""" | ||
|
|
||
| BASE_URL = "https://api.openalex.org" | ||
|
|
||
| def __init__( | ||
| self, | ||
| email: str = None, | ||
| s3_service: S3BucketService = None, | ||
| ) -> None: | ||
| self.email = email | ||
|
|
||
| def request_with_retry( | ||
| self, | ||
| endpoint: str, | ||
| params: dict = None, | ||
| max_retries: int = 3, | ||
| timeout: int = 30, | ||
| stream: bool = False, | ||
| ) -> requests.Response: | ||
| """Make an HTTP GET request with retry logic for rate limits and server errors.""" | ||
| url = endpoint if endpoint.startswith("http") else urljoin(self.BASE_URL, endpoint) | ||
| for attempt in range(max_retries): | ||
| try: | ||
| response = requests.get(url, params=params, timeout=timeout, stream=stream) | ||
| if response.status_code == 200: | ||
| return response | ||
| if response.status_code == 403 or response.status_code >= 500: | ||
| wait_time = 2 ** attempt | ||
| time.sleep(wait_time) | ||
| else: | ||
| response.raise_for_status() | ||
| except requests.exceptions.Timeout: | ||
| if attempt < max_retries - 1: | ||
| logging.info(f"Retrying... Attempt {attempt + 2}") | ||
| time.sleep(2 ** attempt) | ||
| else: | ||
| raise | ||
|
|
||
| raise Exception(f"Failed after {max_retries} retries") | ||
|
|
||
| def search_works( | ||
| self, | ||
| keywords: str = None, | ||
| author_id: str = None, | ||
| institution_id: str = None, | ||
| source_id: str = None, | ||
| publication_year: str = None, | ||
| open_access: bool = True, | ||
| has_pdf: bool = True, | ||
| limit: int = 10, | ||
| sort: str = None | ||
| ): | ||
| """ | ||
| Search for works in OpenAlex based on various filters. | ||
|
|
||
| Args: | ||
| keywords: Search keywords for title and abstract, e.g. 'machine learning drug discovery' | ||
| author_id: Filter by OpenAlex author ID | ||
| institution_id: Filter by OpenAlex institution ID | ||
| source_id: Filter by OpenAlex source ID | ||
| publication_year: Filter by publication year (e.g., ">2020", "2020-2022") | ||
| open_access: Whether to filter for open access papers | ||
| has_pdf: Whether to filter for papers with PDF available | ||
| limit: Number of results to return (max 200) | ||
| sort: Sorting criterion (e.g., "publication_date", "cited_by_count:desc") | ||
| """ | ||
| filters = [] | ||
|
|
||
| if keywords: | ||
| filters.append(f"title.search:{keywords.replace(' ', '+')}") | ||
| if author_id: | ||
| filters.append(f"author.id:{author_id}") | ||
| if institution_id: | ||
| filters.append(f"institution.id:{institution_id}") | ||
| if source_id: | ||
| filters.append(f"primary_location.source.id:{source_id}") | ||
| if publication_year: | ||
| filters.append(f"publication_year:{publication_year}") | ||
| if open_access: | ||
| filters.append("is_oa:true") | ||
| if has_pdf: | ||
| filters.append("has_content.pdf:true") | ||
|
|
||
|
|
||
| params = {"filter": ",".join(filters)} | ||
|
|
||
| if sort: | ||
| params["sort"] = sort | ||
|
|
||
| params["per-page"] = limit | ||
|
|
||
| return self.request_with_retry(endpoint="works", params=params).json() | ||
|
|
||
| def search_entity( | ||
| self, | ||
| entity_type: str, | ||
| entity_name: str | ||
| ) -> dict: | ||
| """ | ||
| Search for an entity ID (author, source, institution) by its name in OpenAlex. | ||
|
|
||
| Args: | ||
| entity_type: Type of entity to search for ("author", "source", "institution") | ||
| entity_name: Name of the entity to search for, e.g., author name, journal name, | ||
| or institution name | ||
| Returns: | ||
| Dictionary containing the most relevant search result for the specified entity | ||
| """ | ||
| endpoint_map = { | ||
| "author": "authors", | ||
| "source": "sources", | ||
| "institution": "institutions" | ||
| } | ||
| if entity_type not in endpoint_map: | ||
| raise ValueError(f"Unsupported entity type: {entity_type}") | ||
|
|
||
| params = {"search": entity_name, "per-page": 1} | ||
| response = self.request_with_retry( | ||
| endpoint=endpoint_map[entity_type], | ||
| params=params | ||
| ).json() | ||
| return response.get("results", [])[0] | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| client = OpenAlexClient(email=OPENALEX_EMAIL) | ||
| # Example works search: | ||
| result = client.search_works( | ||
| institution_id="i173089394", # Replace with a valid institution ID | ||
| publication_year="2025", | ||
| limit=1, | ||
| sort="cited_by_count:desc" | ||
| ) | ||
| # Example entity search: | ||
| # result = client.search_entity( | ||
| # entity_type="institution", | ||
| # entity_name="ITMO University" | ||
| # ) | ||
| pprint(result) |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.