Skip to main content
Glama
devShahriar

openobserve-mcp

by devShahriar
README.md
# openobserve-mcp

An [MCP](https://modelcontextprotocol.io) server that lets any MCP client — Claude Code, Cursor, a custom agent — query an [OpenObserve](https://openobserve.ai) instance: logs, traces, and metrics.

## Quick start

```bash
npm install
npm run build

npm run o2:up     # OpenObserve on http://localhost:5080
npm run seed      # sample logs, traces, and metrics
npm run smoke     # call every tool over a real stdio MCP session
```

`npm run smoke` should print eight `ok` lines. Log in to the UI at http://localhost:5080 with `root@example.com` / `Complexpass#123`.

## Connecting a client

### Claude Code

```bash
claude mcp add openobserve \
  --env O2_URL=http://localhost:5080 \
  --env O2_ORG=default \
  --env O2_USER=root@example.com \
  --env O2_PASSWORD='Complexpass#123' \
  -- node /absolute/path/to/openobserve-mcp/dist/index.js
```

Restart the client afterwards — MCP servers are loaded at session start.

### Any client (JSON config)

```json
{
  "mcpServers": {
    "openobserve": {
      "command": "node",
      "args": ["/absolute/path/to/openobserve-mcp/dist/index.js"],
      "env": {
        "O2_URL": "http://localhost:5080",
        "O2_ORG": "default",
        "O2_USER": "root@example.com",
        "O2_PASSWORD": "Complexpass#123"
      }
    }
  }
}
```

## Configuration

| Variable | Default | Purpose |
|---|---|---|
| `O2_URL` | `http://localhost:5080` | Base URL of the instance |
| `O2_ORG` | `default` | Organization id |
| `O2_USER` | — | Login email |
| `O2_PASSWORD` | — | Password |

See `.env.example`. Credentials are read from the process environment only; nothing is written to disk.

## Tools

| Tool | Purpose |
|---|---|
| `list_streams` | Enumerate streams, optionally filtered by type |
| `get_schema` | Field names and types for a stream |
| `get_org_summary` | Stream count, storage, pipelines, alerts, dashboards |
| `search_logs` | Rows matching a SQL `WHERE` clause, newest first |
| `aggregate_logs` | `COUNT` / `AVG` / `GROUP BY` over logs, traces, or metrics |
| `search_traces` | Recent traces with root operation, duration, services |
| `get_trace` | Every span of one trace, ordered by start time |
| `query_metrics` | PromQL over a time range |

### Time ranges

Every time argument takes an ISO timestamp or relative shorthand: `30s`, `15m`, `2h`, `7d`, `1w`. `end_time` defaults to now.

### `aggregate_logs` table token

Write the table as the literal token `stream`; it is substituted with the real stream name in `FROM` / `JOIN` position only, so a column or string literal containing the word is left alone.

```json
{
  "stream": "app_logs",
  "sql": "SELECT service, COUNT(*) AS c FROM stream GROUP BY service ORDER BY c DESC",
  "start_time": "1h"
}
```

Logs and traces live in separate indexes. To aggregate spans, pass `"stream_type": "traces"` — otherwise OpenObserve reports `stream not found`.

## Layout

```
src/
  index.ts          entrypoint — stdio transport only
  server.ts         builds a wired McpServer (importable for tests)
  config.ts         environment → Config
  client.ts         OpenObserve HTTP API wrapper
  time.ts           ISO / relative range parsing → micros + seconds
  tools/
    index.ts        registers every tool
    helpers.ts      JSON result shape, SQL quoting, shared schema
    streams.ts      list_streams, get_schema, get_org_summary
    logs.ts         search_logs, aggregate_logs
    traces.ts       search_traces, get_trace
    metrics.ts      query_metrics
scripts/
  seed.mjs          sample logs, traces, metrics
  smoke.mjs         stdio MCP client — exercises every tool
docker-compose.yml  local OpenObserve
```

Adding a tool means one file in `src/tools/` plus a line in `src/tools/index.ts`.

## Verified against

OpenObserve `public.ecr.aws/zinclabs/openobserve:latest` (v0.15.x), MCP SDK 1.25, Node 22.

Endpoint paths are version-sensitive. Two in particular differ from what the API docs suggest: stream schema is `/api/{org}/streams/{stream}/schema` (not `/api/{org}/{stream}/schema`), and there is **no** `/traces/{trace_id}` endpoint — `get_trace` searches the trace stream by id instead. If you upgrade OpenObserve, re-run `npm run smoke`.

## Development

```bash
npm run watch      # tsc --watch
npm run smoke      # regression check against a live instance
node scripts/smoke.mjs list                    # inspect registered tools
node scripts/smoke.mjs get_schema '{"stream":"app_logs"}'
```

`scripts/smoke.mjs` speaks the real MCP protocol over stdio, so it catches schema and transport regressions that a direct HTTP test would miss.

TDQS

A4/5.0

Scored across 8 tools

Disambiguation5/5

Each tool targets a distinct aspect of OpenObserve: streams, schema, org summary, logs, traces, metrics. Search vs. aggregate vs. query are clearly differentiated by descriptions, and search_traces vs. get_trace are separate listing vs. detail operations.

Naming Consistency5/5

All tool names use snake_case with a verb_noun structure (list_, get_, search_, aggregate_, query_). The verbs accurately reflect the operation, and the nouns denote the target resource. This is highly predictable.

Tool Count5/5

With 8 tools covering streams, schema, logs, traces, and metrics, the surface is well-scoped without being bloated. Each tool fills a necessary role for an observability MCP server.

Completeness5/5

The set provides a comprehensive read-only query surface: listing streams, inspecting schemas, searching and aggregating logs, listing traces with details, and querying metrics. No obvious dead ends or missing critical operations for the core domain.

Maintenance

ActivitySlowing
ResponsivenessNo issues