Skip to content

Commit 2834207

Browse files
merge conf
2 parents 767f4ea + a796979 commit 2834207

58 files changed

Lines changed: 1713 additions & 162 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/testing.yml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,8 @@ jobs:
3333
- name: Install Python dependencies
3434
run: |
3535
python -m pip install --upgrade "pip<25.3"
36-
pip install -r requirements/requirements-${{ matrix.os }}_py${{ matrix.python-version }}.txt
3736
pip install -r requirements/requirements-${{ matrix.os }}_py${{ matrix.python-version }}_extras.txt
37+
pip install -e . --no-deps
3838
3939
- name: Set SSL_CERT_FILE (Linux)
4040
if: matrix.os == 'ubuntu-latest' || matrix.os == 'macos-latest'
@@ -59,7 +59,6 @@ jobs:
5959
MP_API_KEY: ${{ secrets[env.API_KEY_NAME] }}
6060
#MP_API_ENDPOINT: https://api-preview.materialsproject.org/
6161
run: |
62-
pip install -e .
6362
pytest -n auto -x --cov=mp_api --cov-report=xml
6463
- uses: codecov/codecov-action@v1
6564
with:

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,3 +121,4 @@ _autosummary
121121

122122
uv.lock
123123
JANAF_*_data.json
124+
.gemini

dev/generate_mcp_tools.py

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
"""Define utilities for (mostly) auto-generating MCP tools.
2+
3+
This file will autogenerate a (Fast)MCP set of tools with
4+
type annotations.
5+
6+
The resultant tools are perhaps too general for use in an MCP.
7+
"""
8+
9+
from __future__ import annotations
10+
11+
from typing import TYPE_CHECKING
12+
13+
from mp_api.client import MPRester
14+
15+
if TYPE_CHECKING:
16+
from collections.abc import Callable
17+
from pathlib import Path
18+
19+
20+
def get_annotation_signature(
21+
obj: Callable, tablen: int = 4
22+
) -> tuple[str | None, str | None]:
23+
"""Reconstruct the type annotations associated with a Callable.
24+
25+
Returns the type annotations on input, and the output
26+
kwargs as str if type annotations can be inferred.
27+
"""
28+
kwargs = None
29+
out_kwargs = None
30+
if (annos := obj.__annotations__) and (defaults := obj.__defaults__):
31+
non_ret_type = [k for k in annos if k != "return"]
32+
defaults = [f" = {val}" for val in defaults]
33+
if len(defaults) < len(non_ret_type):
34+
defaults = [""] * (len(non_ret_type) - len(defaults)) + defaults
35+
kwargs = ",\n".join(
36+
f"{' '*tablen}{k} : {v}{defaults[i]}"
37+
for i, (k, v) in enumerate(annos.items())
38+
if k != "return"
39+
)
40+
out_kwargs = ",\n".join(
41+
f"{' '*2*tablen}{k} = {k}" for k in annos if k != "return"
42+
)
43+
return kwargs, out_kwargs
44+
45+
46+
def regenerate_tools(
47+
client: MPRester | None = None, file_name: str | Path | None = None
48+
) -> str:
49+
"""Utility to regenerate the informative tool names with annotations."""
50+
func_str = """# ruff: noqa
51+
from __future__ import annotations
52+
53+
from datetime import datetime
54+
from typing import Literal
55+
56+
from emmet.core.chemenv import (
57+
COORDINATION_GEOMETRIES,
58+
COORDINATION_GEOMETRIES_IUCR,
59+
COORDINATION_GEOMETRIES_IUPAC,
60+
COORDINATION_GEOMETRIES_NAMES,
61+
)
62+
from emmet.core.electronic_structure import BSPathType, DOSProjectionType
63+
from emmet.core.grain_boundary import GBTypeEnum
64+
from emmet.core.mpid import MPID
65+
from emmet.core.thermo import ThermoType
66+
from emmet.core.summary import HasProps
67+
from emmet.core.symmetry import CrystalSystem
68+
from emmet.core.synthesis import SynthesisTypeEnum, OperationTypeEnum
69+
from emmet.core.vasp.calc_types import CalcType
70+
from emmet.core.xas import Edge, Type
71+
72+
from pymatgen.analysis.magnetism.analyzer import Ordering
73+
from pymatgen.core.periodic_table import Element
74+
from pymatgen.core.structure import Structure
75+
from pymatgen.electronic_structure.core import OrbitalType, Spin
76+
77+
"""
78+
79+
translate = {
80+
"chemenv": "chemical_environment",
81+
"dos": "density_of_states",
82+
"eos": "equation_of_state",
83+
"summary": "material",
84+
"robocrys": "crystal_summary",
85+
}
86+
87+
mp_client = client or MPRester()
88+
89+
def _get_rester_sub_name(name, route) -> str | None:
90+
for y in [x for x in dir(route) if not x.startswith("_")]:
91+
attr = getattr(route, y)
92+
if (
93+
(hasattr(attr, "__name__") and attr.__name__ == name)
94+
or (hasattr(attr, "__class__"))
95+
and attr.__class__.__name__ == name
96+
):
97+
return y
98+
return None
99+
100+
for x in mp_client._all_resters:
101+
if not (
102+
sub_rest_route := _get_rester_sub_name(x.__name__, mp_client.materials)
103+
):
104+
continue
105+
106+
search_method = "search"
107+
if "robocrys" in x.__name__.lower():
108+
search_method = "search_docs"
109+
110+
informed_name = sub_rest_route
111+
for k, v in translate.items():
112+
if k in informed_name:
113+
informed_name = informed_name.replace(k, v)
114+
115+
kwargs, out_kwargs = get_annotation_signature(getattr(x, search_method))
116+
if not kwargs:
117+
# FastMCP raises a ValueError if types are not provided:
118+
# `Functions with **kwargs are not supported as tools`
119+
continue
120+
func_str += (
121+
f"def get_{informed_name}_data(\n"
122+
f" self,\n{kwargs}\n) -> list[dict]:\n"
123+
f" return self.client.materials.{sub_rest_route}"
124+
f".search(\n{out_kwargs}\n)\n\n"
125+
)
126+
127+
helpers = [
128+
method
129+
for method in dir(mp_client)
130+
if any(
131+
method.startswith(signature)
132+
for signature in (
133+
"get",
134+
"find",
135+
)
136+
)
137+
]
138+
for func_name in helpers:
139+
func = getattr(mp_client, func_name)
140+
# MCP doesn't work with LRU cached functions?
141+
if hasattr(func, "cache_info"):
142+
continue
143+
144+
kwargs, out_kwargs = get_annotation_signature(func)
145+
if not kwargs:
146+
continue
147+
148+
informed_name = func_name.replace("find", "get")
149+
for k, v in translate.items():
150+
if k in informed_name:
151+
informed_name = informed_name.replace(k, v)
152+
153+
func_str += (
154+
f"def {informed_name}(\n"
155+
f" self,\n{kwargs}\n) -> list[dict]:\n"
156+
f" return self.client.{func_name}(\n"
157+
f"{out_kwargs}\n)\n\n"
158+
)
159+
160+
if file_name:
161+
with open(file_name, "w") as f:
162+
f.write(func_str)
163+
164+
return func_str

dev/inspect_mcp.sh

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
#!/bin/bash -l
2+
3+
# Tool to run the MCP inspector:
4+
# https://modelcontextprotocol.io/docs/tools/inspector
5+
6+
server_path=$(python -c 'from importlib_resources import files ; print(str((files("mp_api.client") / ".."/ "..").resolve()))')
7+
8+
fastmcp dev \
9+
--python 3.12 \
10+
--with-editable $server_path \
11+
--with-requirements "$server_path/requirements/requirements-ubuntu-latest_py3.12_extras.txt" \
12+
"$server_path/mp_api/mcp/server.py"

mp_api/mcp/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
"""Get default MCP for Materials Project."""

0 commit comments

Comments
 (0)