Skip to main content
Glama
README.md
# 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).

TDQS

A4.3/5.0

Scored across 5 tools

Disambiguation4/5

Each tool maps to a distinct operation (listing logbooks, searching, reading a single entry, browsing recent entries, fetching attachments), so confusion is unlikely. However, elog_get_recent_entries overlaps with elog_search's last_days and reverse options, making the boundary between them slightly less sharp.

Naming Consistency4/5

The elog_ prefix and verb-first style are consistent, and most tools follow a verb_noun pattern such as elog_list_logbooks, elog_get_entry, and elog_get_attachment. elog_search is the one outlier because it omits an explicit object, which is a minor deviation from the otherwise predictable pattern.

Tool Count5/5

Five tools is a well-scoped size for a read-only logbook client. Each tool covers one essential need—discovering logbooks, searching entries, reading full entries, browsing recent entries, and downloading attachments—without unnecessary redundancy or bloat.

Completeness4/5

The read-oriented surface is complete for searching, reading, and retrieving attachments from ELOG entries. The only notable gap is the absence of write or lifecycle tools such as creating, replying to, or editing entries, though this appears to be an intentional read-only scope.

Maintenance

ActivityMaintained
ResponsivenessNo issues