Metadata-Version: 2.5
Name: elog-mcp
Version: 0.3.2
Summary: Read-only MCP server for ELOG electronic logbooks over HTTP
Project-URL: Homepage, https://github.com/ast0815/elog-mcp
Project-URL: Repository, https://github.com/ast0815/elog-mcp
Author: elog-mcp contributors
License: MIT
Keywords: elog,logbook,mcp
Classifier: Development Status :: 3 - Alpha
Classifier: Framework :: AsyncIO
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Requires-Python: >=3.10
Requires-Dist: httpx>=0.27
Requires-Dist: mcp<2,>=1.10
Requires-Dist: pydantic-settings>=2.3
Requires-Dist: pydantic>=2.7
Provides-Extra: openapi
Requires-Dist: fastapi>=0.115; extra == 'openapi'
Requires-Dist: uvicorn>=0.30; extra == 'openapi'
Description-Content-Type: text/markdown

# elog-mcp

[OpenAPI](https://spec.openapis.org)/[MCP](https://modelcontextprotocol.io)
server giving LLM agents **read-only** access to [ELOG](https://elog.psi.ch/elog/)
electronic logbooks over HTTP. Works with any standard elogd instance.

- Search entries by full text (regex), attributes, and date range
- Read entries in full (attributes + body + attachments)
- Download attachments, discover logbooks

Strictly read-only: only HTTP GET requests are issued. TLS verification is on
by default. Credentials come from the environment only and are never logged or
echoed.

## Installation

Requires Python >= 3.10.

**uvx** (recommended -- runs elog-mcp in an isolated environment, no setup):

```bash
uvx elog-mcp
```

**pipx**:

```bash
pipx install elog-mcp
```

**pip**:

```bash
pip install elog-mcp
```

For the OpenAPI/REST transport, install with the `openapi` extra:

```bash
uvx --extra openapi elog-mcp
pipx install "elog-mcp[openapi]"
pip install "elog-mcp[openapi]"
```

**From source** (development):

```bash
git clone https://github.com/ast0815/elog-mcp.git
cd elog-mcp
uv sync
uv run elog-mcp
```

## Configuration

All settings are environment variables (`ELOG_` prefix):

| Variable | Required | Meaning |
|---|---|---|
| `ELOG_URL` | yes | Base URL incl. subdir, e.g. `https://elog.example.org/elog` |
| `ELOG_USER` / `ELOG_PASSWORD` | no | Shared credentials |
| `ELOG_LOGBOOKS` | no | Comma-separated allow-list used by search `*` / listing order |
| `ELOG_SSL_VERIFY` | no | `true` (default) / `false` — never disable outside local tests |
| `ELOG_TIMEOUT` | no | HTTP timeout seconds (default `30`) |
| `ELOG_MCP_TRANSPORT` | no | `stdio` (default) · `streamable-http` · `openapi` |
| `ELOG_MCP_HOST` / `ELOG_MCP_PORT` | no | Bind address/port in HTTP modes (default `127.0.0.1:8000`) |
| `ELOG_API_KEY` | no | Require `Authorization: Bearer <key>` on all OpenAPI endpoints |
| `ELOG_CORS_ORIGINS` | no | Comma-separated CORS origins for openapi mode (default `*`) |
| `ELOG_MCP_CERTFILE` / `ELOG_MCP_KEYFILE` | no | TLS cert/key (PEM) — serves HTTPS in HTTP modes |

Common prefix for every deployment:

```bash
export ELOG_URL=https://elog.example.org/elog
export ELOG_USER=your-shared-user
export ELOG_PASSWORD=your-shared-password
```

## Deployment

`ELOG_MCP_TRANSPORT` picks how clients talk to the server:

| Transport | Clients | Endpoint |
|---|---|---|
| `stdio` (default) | Claude Desktop, opencode, other local MCP hosts | spawned process |
| `streamable-http` | Web UIs speaking MCP over HTTP (LibreChat, …) | `http://<host>:<port>/mcp` |
| `openapi` | OpenAPI tool servers (Open WebUI, …) | spec at `/openapi.json`, docs at `/docs`, base `/` answers a liveness JSON |

### stdio (local MCP clients)

The command you put in the client config depends on how you installed
elog-mcp:

| Installation | Command |
|---|---|
| **uvx** (no install needed) | `uvx elog-mcp` |
| **pipx** | `elog-mcp` (or full path — see below) |
| **pip / from source** | `elog-mcp` (must be on `PATH`) |
| **From source (dev)** | `uv run elog-mcp` |

**Finding the binary path.** If `which elog-mcp` does not print a path (e.g.
you installed with pipx but your client runs in a different shell environment),
use:

```bash
pipx runpip show elog-mcp | grep Location
# → …/site-packages
# then:  find …/site-packages/../../bin -name elog-mcp
```

or on most systems simply:

```bash
ls ~/.local/bin/elog-mcp
```

Use the absolute path in that case.

---

**Claude Desktop** (`claude_desktop_config.json`):

```json
{
  "mcpServers": {
    "elog": {
      "command": "uvx",
      "args": ["elog-mcp"],
      "env": { "ELOG_URL": "…", "ELOG_USER": "…", "ELOG_PASSWORD": "…" }
    }
  }
}
```

If you installed with **pipx** and prefer the standalone binary:

```json
{
  "mcpServers": {
    "elog": {
      "command": "/home/YOU/.local/bin/elog-mcp",
      "env": { "ELOG_URL": "…", "ELOG_USER": "…", "ELOG_PASSWORD": "…" }
    }
  }
}
```

---

**opencode** (`~/.config/opencode/opencode.json`):

```json
{
  "mcp": {
    "elog": {
      "type": "local",
      "command": ["uvx", "elog-mcp"],
      "environment": { "ELOG_URL": "…", "ELOG_USER": "…", "ELOG_PASSWORD": "…" }
    }
  }
}
```

If you installed with **pipx** and prefer the standalone binary:

```json
{
  "mcp": {
    "elog": {
      "type": "local",
      "command": ["/home/YOU/.local/bin/elog-mcp"],
      "environment": { "ELOG_URL": "…", "ELOG_USER": "…", "ELOG_PASSWORD": "…" }
    }
  }
}
```

---

From source (without installing):

```bash
uv run elog-mcp
```

### streamable-http (remote MCP clients)

```bash
ELOG_MCP_TRANSPORT=streamable-http elog-mcp
```

Register `http://127.0.0.1:8000/mcp` as a remote MCP server in your UI.

### openapi (Open WebUI & friends)

```bash
ELOG_MCP_TRANSPORT=openapi \
ELOG_API_KEY=pick-a-random-secret \
elog-mcp
```

Five read-only endpoints mirror the tools below (operationIds equal the tool
names). In Open WebUI: **Settings → Tools → +** (user-level, fetched by your
browser) or **Admin Settings → Tools** (global, fetched by its backend), enter
the server URL, and put your `ELOG_API_KEY` value in the Bearer key field.

**HTTPS:** pass `ELOG_MCP_CERTFILE`/`ELOG_MCP_KEYFILE`, or put a reverse proxy
or tunnel (e.g. `cloudflared tunnel --url http://localhost:8000`) in front.
For locally-trusted dev certs: `mkcert -install && mkcert localhost 127.0.0.1 ::1`,
then feed the two files to the variables above (absolute paths).

**Reachability rules of thumb:**

- Everything binds `127.0.0.1` by default. Set `ELOG_MCP_HOST=0.0.0.0` and an
  `ELOG_API_KEY` whenever someone else must reach the server.
- Client in Docker? `127.0.0.1` inside its container is not your machine — use
  `http://host.docker.internal:<port>` (`--add-host=host.docker.internal:host-gateway`
  on Linux).
- Hosted instance (e.g. a university WebUI)? It can never reach your laptop's
  loopback. User-level tool servers are additionally subject to that site's
  Content-Security-Policy — if DevTools shows the request as
  **Transferred: CSP**, the browser blocked it and only an IT-side
  `connect-src` change helps. The practical route: deploy elog-mcp somewhere
  reachable and let the admins register it once as a global tool server.

## Tools

Same five operations on every transport (REST routes shown for `openapi`):

| Tool | REST route | Purpose |
|---|---|---|
| `elog_list_logbooks()` | `GET /logbooks` | List logbooks |
| `elog_search(text?, attributes?, date_from?, date_to?, last_days?, logbook="*", max_results=50, reverse=true)` | `GET /search` (`attributes` = JSON object string) | Regex full-text + attribute + date filtering; returns excerpts |
| `elog_get_entry(logbook, entry_id)` | `GET /logbooks/{logbook}/entries/{entry_id}` | Full entry body, attributes, threading |
| `elog_get_recent_entries(logbook, count=20)` | `GET /logbooks/{logbook}/recent?count=N` | Newest-first shortcut |
| `elog_get_attachment(logbook, filename)` | `GET /logbooks/{logbook}/attachments/{filename}` | Base64 attachment content |

Notes:

- Text and attribute filters are **regular expressions** (elogd semantics);
  `^value$` anchors give exact matches.
- Dates: `YYYY-MM-DD[ HH:MM[:SS]]`, or use `last_days` instead of a range;
  `logbook="*"` searches all known logbooks.
- Errors surface uniformly: `EntryNotFound`, `AuthFailed`, `ServerError`,
  `NetworkError`, `InvalidLogbook` (REST status codes: 404/502/502/503/404,
  invalid query parameters → 400/422).

## Development

```bash
uv run pytest                # unit tests, offline
uv run pytest -m live        # live suite (needs ELOG_TEST_URL)
uv run ruff check .          # lint
uv run ruff format --check . # formatting gate
uv run mypy src              # type check (strict)
```

Protocol details and wire-format references live in [`RESOURCES.md`](RESOURCES.md);
architecture and tool contracts in [`SPEC.md`](SPEC.md).
