openobserve-mcp
# 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
Scored across 8 tools
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.
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.
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.
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.