diff --git a/README.md b/README.md index 0994bc2..3dd6afe 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,82 @@ # EVE MCP -An MCP server for EVE. +An [MCP](https://modelcontextprotocol.io/) server that exposes the +[EVE](https://eve-chat.chat) Earth Observation chat API as a set of MCP +tools. +> **Status: alpha (v0.0.1) — work in progress.** APIs, tool names, and +> wire formats may change without notice. Not yet recommended for +> production use. + +## Tools + +The server registers the following MCP tools: + +- `query_eve` — query the EVE RAG system and return the assembled answer +- `list_eve_collections` — list publicly available document collections +- `check_eve_health` — unauthenticated health check against the EVE API +- `extract_factuality_issues` — analyze a Google Earth Engine Python + script and surface scientific assumptions worth verifying +- `assess_factuality_issue` — produce an expert-style assessment of an + issue raised against a GEE script + +## Installation + +The project uses [Poetry](https://python-poetry.org/) and a Git +submodule for the upstream `eve-api` client. + +```bash +git clone --recurse-submodules https://github.com//eve-mcp.git +cd eve-mcp +poetry install +``` + +If you cloned without `--recurse-submodules`: + +```bash +git submodule update --init --recursive +``` + +Supported Python versions: 3.11–3.14. + +## Configuration + +The server authenticates to EVE on first tool invocation using the +following environment variables: + +| Variable | Required | Default | Purpose | +| --------------- | -------- | ----------------------------- | -------------------------------- | +| `EVE_EMAIL` | yes | — | EVE account email | +| `EVE_PASSWORD` | yes | — | EVE account password | +| `EVE_BASE_URL` | no | `https://api.eve-chat.chat` | EVE API base URL | + +## Running the server + +Over stdio (the standard MCP transport): + +```bash +poetry run python -m eve_mcp.server +``` + +## Development + +Install the pre-commit hooks once: + +```bash +poetry run pre-commit install +``` + +Run the full hook suite (black, isort, mypy, pylint, pytest+coverage) +against all files: + +```bash +poetry run pre-commit run --all-files +``` + +## License + +MIT — see [LICENSE](LICENSE). + +## Copyright + +© 2026 Trillium Technologies Ltd. diff --git a/src/eve_mcp/__init__.py b/src/eve_mcp/__init__.py index e8ca9fc..3f0f05a 100644 --- a/src/eve_mcp/__init__.py +++ b/src/eve_mcp/__init__.py @@ -1,5 +1 @@ -"""EVE MCP server and client.""" - -from .client import EVEMCPClient - -__all__ = ["EVEMCPClient"] +"""EVE MCP server.""" diff --git a/src/eve_mcp/client.py b/src/eve_mcp/client.py deleted file mode 100644 index c9158b5..0000000 --- a/src/eve_mcp/client.py +++ /dev/null @@ -1,109 +0,0 @@ -"""MCP client for the EVE server (server via stdio).""" - -from __future__ import annotations - -import json -import os -from contextlib import AsyncExitStack -from typing import Any - -from mcp import ( # pylint: disable=wrong-import-order - ClientSession, - StdioServerParameters, -) -from mcp.client.stdio import stdio_client # pylint: disable=wrong-import-order - -from .config import PYTHON_BIN, PYTHON_PATH, SERVER_MODULE - - -class EVEToolError(RuntimeError): - """Raised when the MCP server returns an error for a tool call.""" - - -class EVEMCPClient: - """Async context manager wrapping an MCP stdio session. - - Usage:: - - async with EVEMCPClient() as eve: - health = await eve.check_health() - answer = await eve.query("What is Earth Observation?") - """ - - def __init__(self) -> None: - self._session: ClientSession | None = None - self._stack: AsyncExitStack | None = None - - async def __aenter__(self) -> EVEMCPClient: - params = StdioServerParameters( - command=PYTHON_BIN, - args=["-m", SERVER_MODULE], - env={**os.environ, "PYTHONPATH": PYTHON_PATH}, - ) - stack = AsyncExitStack() - self._stack = await stack.__aenter__() - read, write = await stack.enter_async_context(stdio_client(params)) - session = ClientSession(read, write) - self._session = await stack.enter_async_context(session) - await self._session.initialize() - return self - - async def __aexit__( - self, - exc_type: type[BaseException] | None, - exc_val: BaseException | None, - exc_tb: Any, - ) -> None: - if self._stack is not None: - await self._stack.__aexit__(exc_type, exc_val, exc_tb) - self._stack = None - self._session = None - - @staticmethod - def _parse_result(result: Any) -> Any: - """Extract JSON from a tool result, raising on server errors.""" - text = result.content[0].text - if result.isError: - raise EVEToolError(text) - return json.loads(text) - - async def query( - self, - question: str, - collections: str = "eve-public", - k: int = 5, - ) -> dict: - """Call ``query_eve`` on the MCP server.""" - assert self._session is not None, "Use as async context manager" - result = await self._session.call_tool( - "query_eve", - { - "question": question, - "collections": collections, - "k": k, - }, - ) - return self._parse_result(result) - - async def extract_factuality_issues( - self, question: str, python_code: str - ) -> str: - """Call ``extract_factuality_issues`` on the MCP server.""" - assert self._session is not None, "Use as async context manager" - result = await self._session.call_tool( - "extract_factuality_issues", - {"question": question, "python_code": python_code}, - ) - return self._parse_result(result) - - async def list_collections(self) -> list: - """Call ``list_eve_collections`` on the MCP server.""" - assert self._session is not None, "Use as async context manager" - result = await self._session.call_tool("list_eve_collections", {}) - return self._parse_result(result) - - async def check_health(self) -> dict: - """Call ``check_eve_health`` on the MCP server.""" - assert self._session is not None, "Use as async context manager" - result = await self._session.call_tool("check_eve_health", {}) - return self._parse_result(result) diff --git a/src/eve_mcp/config.py b/src/eve_mcp/config.py index a7f4814..b14f456 100644 --- a/src/eve_mcp/config.py +++ b/src/eve_mcp/config.py @@ -1,15 +1,3 @@ -"""This file contains module constants.""" +"""Module-level configuration constants.""" -import os -import sys -from pathlib import Path - -PYTHON_BIN = os.getenv( - "EVE_MCP_PYTHON", - sys.executable, -) SERVER_MODULE = "eve_mcp.server" -PYTHON_PATH = str(Path(__file__).resolve().parent.parent) -PYTHON_PATH += ":" + os.path.join( - str(Path(__file__).parent.parent.parent), "lib", "eve-api", "src" -) diff --git a/tests/test_client.py b/tests/test_client.py deleted file mode 100644 index 2e11d9e..0000000 --- a/tests/test_client.py +++ /dev/null @@ -1,5 +0,0 @@ -"""This file tests the client module.""" - - -def test_pass(): - """A dummy test to automatically pass, so CI/CD can be built incrementally."""