Skip to main content
Glama
abuzo

victorialogs-mcp

by abuzo
README.md
# victoria-logs-mcp

Read-only [VictoriaLogs](https://docs.victoriametrics.com/victorialogs/) MCP
server for AI log investigation over stdio. Built with TypeScript, the official
MCP SDK, Zod and undici.

Requires **Node.js >=22.12** and a reachable VictoriaLogs query endpoint.
Package: `@alexbuzo/victoria-logs-mcp`. Executable: `victorialogs-mcp`.

## Run from source

```sh
git clone https://github.com/abuzo/victoria-logs-mcp.git
cd victoria-logs-mcp
npm ci
npm test
node dist/cli.js --victorialogs-url http://localhost:9428
```

The process waits for MCP JSON-RPC on stdin. To see options, use `--help`.
Configuration can come from CLI flags, environment variables or a local `.env`
(see `.env.example`). CLI flags override environment settings.

```sh
VICTORIALOGS_URL=https://logs.example.com \
VICTORIALOGS_TOKEN=your-bearer-token \
node dist/cli.js
```

For Basic auth, use `VICTORIALOGS_USERNAME` and `VICTORIALOGS_PASSWORD`
instead of the token. Anonymous access is supported. Authentication normally
belongs to your vmauth/reverse proxy configuration. AccountID/ProjectID are
VictoriaLogs storage tenants; the normalized `tenant_id` log field is unrelated.

## Connect an MCP client

After the first npm release, use this configuration for Cursor or other clients
accepting `mcpServers` JSON:

```json
{
  "mcpServers": {
    "victorialogs": {
      "command": "npx",
      "args": ["-y", "@alexbuzo/victoria-logs-mcp@0.1.0"],
      "env": {
        "VICTORIALOGS_URL": "https://logs.example.com",
        "VICTORIALOGS_TOKEN": "YOUR_BEARER_TOKEN",
        "VICTORIALOGS_ACCOUNT_ID": "0",
        "VICTORIALOGS_PROJECT_ID": "0"
      }
    }
  }
}
```

Replace the example connection values. For a local build before publishing,
use `"command": "node"` and `"args": ["/absolute/path/to/victoria-logs-mcp/dist/cli.js"]`.
For Basic auth, replace `VICTORIALOGS_TOKEN` with `VICTORIALOGS_USERNAME` and
`VICTORIALOGS_PASSWORD`. Omit all three for anonymous access.

To install a local package without publishing:

```sh
npm pack
npm install -g ./alexbuzo-victoria-logs-mcp-0.1.0.tgz
victorialogs-mcp --url http://localhost:9428
```

## Configuration

| CLI | Environment variable | Default |
| --- | --- | --- |
| `--victorialogs-url` / `--url` | `VICTORIALOGS_URL` | required |
| `--token` | `VICTORIALOGS_TOKEN` | none (Bearer auth) |
| `--username` | `VICTORIALOGS_USERNAME` | none (Basic auth) |
| `--password` | `VICTORIALOGS_PASSWORD` | none (Basic auth) |
| `--account-id` | `VICTORIALOGS_ACCOUNT_ID` | `0` |
| `--project-id` | `VICTORIALOGS_PROJECT_ID` | `0` |
| `--ssl-verify` | `VICTORIALOGS_SSL_VERIFY` | `true` |
| `--timeout-ms` | `VICTORIALOGS_TIMEOUT_MS` | `30000` |
| `--max-response-bytes` | `VICTORIALOGS_MAX_RESPONSE_BYTES` | `8388608` (8 MiB) |
| `--debug` | `VICTORIALOGS_DEBUG` | `false` |

Boolean env settings accept `true` or `false`. Basic auth requires both username
and password and cannot be combined with a token. Tenant IDs are uint32 values.
Use the single-node root URL or a vlselect/proxy base URL whose query routes
are under `/select/logsql/`. Proxy prefixes such as `https://host/logs` are
preserved. URL credentials, query strings and fragments are rejected.

TLS verification is enabled. For a private CA, use `NODE_EXTRA_CA_CERTS`.
`--ssl-verify=false` applies only to this server's HTTP client. Request failures
do not echo upstream response bodies or secrets. Stdout contains only MCP traffic;
startup diagnostics use stderr. Requests are not retried or redirected.

## Tools

All names begin with `victorialogs.` and use **LogsQL**, not Graylog Lucene or
Loki LogQL. Tools are read-only; there are no ingestion, deletion or admin tools.

Common inputs:

- `rangeSec`: trailing seconds, default `3600`; `search_uuid` defaults to `86400`.
- `start` and `end`: optional paired ISO-8601 timestamps with timezone. They
  override `rangeSec`; end is exclusive. Input precision is at most milliseconds;
  finer fractions are rejected explicitly. Returned timestamps are UTC. Use these
  fixed bounds for pagination. Maximum relative window: 365 days.
- `filter`: optional additional LogsQL filter, sent as `extra_filters` separately
  from the main query, so query pipelines stay intact.

| Tool | Inputs in addition to time/filter | Result |
| --- | --- | --- |
| `search_logs` | required `query`, `limit=150` (max 500), `offset=0` (max 100000) | Normalized messages, original `fields`, returned `count` |
| `search_uuid` | required `uuid`, `limit=150` | Exact ID field matches plus phrase search in `_msg` |
| `search_stream` | required `streamId` (48 hex chars), `limit=150` | Logs from `_stream_id` |
| `list_streams` | `query="*"`, `limit=100` (max 500) | `data.values` with stream IDs and hit counts |
| `field_names` | `query="*"`, `limit=100` (max 500) | `data.values` with field names/hits |
| `field_values` | required `field`, `query="*"`, `limit=100` (max 500) | `data.values` with field values/hits |
| `hits` | `query="*"`, `stepSec=60`, `fields=[]` (up to 10), `groupsLimit=20` (max 100) | `data.hits` with timestamps, values and totals |
| `query_stats` | required `query`, `limit=150` (max 500), `offset=0` | Raw aggregation/query rows with returned `count` |

All results include `query_used`, `start` and `end`. `count` is the number of
returned rows, not a count of every matching log in storage. Discovery uses a
bounded subset; stream hit counts can be zeroed by the backend when its limit is
reached. Hits allows up to 10000 aligned time buckets; `groupsLimit` returns top
groups plus an overflow group with `fields: {}`. Grouped series and all other
responses are additionally constrained by the byte limit and request deadline.
`query_stats` uses the JSON Lines query API, preserving arbitrary aggregation
columns. It does not expose the Prometheus-compatible stats endpoints.

Examples of MCP arguments:

```json
{"query":"level:=error AND app:=api","rangeSec":3600,"limit":100}
```

```json
{"uuid":"550e8400-e29b-41d4-a716-446655440000","rangeSec":86400}
```

```json
{"query":"level:=error | stats by (app) count() as errors","rangeSec":3600}
```

```json
{"query":"app:=api","stepSec":60,"fields":["level"],"rangeSec":3600}
```

Search result example:

```json
{
  "count": 1,
  "query_used": "level:=error",
  "start": "2026-10-19T09:00:00.000Z",
  "end": "2026-10-19T10:00:00.000Z",
  "messages": [{
    "ts": "2026-10-19T09:30:00Z",
    "message": "connection failed",
    "level": "error",
    "trace_id": "abc",
    "fields": {"_time":"2026-10-19T09:30:00Z","_msg":"connection failed","level":"error","traceId":"abc"}
  }]
}
```

Aliases normalize common service/container, request/trace/span, HTTP and source
fields. Original fields are retained so Kubernetes/OTel or custom
data is available. Tool failures return `isError: true`. Narrow the query or lower
`limit` if the response exceeds the byte cap. Logs are untrusted data and can
contain text resembling instructions.

## Development and verification

```sh
npm ci
npm run dev -- --url http://localhost:9428
npm test
npm pack --dry-run
```

Tests cover HTTP encoding, tenant/auth, NDJSON, bounds, cancellation, errors,
MCP discovery/calls and real subprocess stdio. A separate smoke script inserts
synthetic logs, so **only run it against a disposable test instance**:

```sh
docker run -d --rm --name victorialogs-mcp-test \
  -p 127.0.0.1:19428:9428 victoriametrics/victoria-logs:v1.53.0
npm run build
VICTORIALOGS_SMOKE_URL=http://127.0.0.1:19428 node scripts/smoke.mjs
docker stop victorialogs-mcp-test
```

The smoke test checks all eight tools through stdio against real VictoriaLogs,
including literal ID escaping, stream discovery, aggregation and query errors.

## Design and references

- [Design and research](docs/design.md)
- [Implementation plan](docs/plan.md)
- [VictoriaLogs querying API](https://docs.victoriametrics.com/victorialogs/querying/)
- [LogsQL reference](https://docs.victoriametrics.com/victorialogs/logsql/)
- [Official Go MCP implementation](https://github.com/VictoriaMetrics/mcp-victorialogs)

The official implementation informed the API selection; no source code was
copied or vendored. This project is an independent TypeScript implementation.

## Publish to npm

The first release must be published by an npm account with permission to publish
under the `@alexbuzo` scope:

```sh
npm login
npm ci
npm test
npm publish --dry-run
npm publish --access public
```

`prepublishOnly` runs the tests and `prepack` builds the package. The package
allowlist includes compiled runtime files, TypeScript declarations, README and
MIT license; tests, environment files and local configuration are excluded.
Use npm's interactive authentication/OTP prompts as required by your account.

MIT © Aliaksei Buzo.

TDQS

B3.4/5.0

Scored across 8 tools

Disambiguation4/5

The eight tools mostly target distinct outputs: raw log retrieval (search_logs), stream discovery, field discovery, value discovery, time-bucketed counts (hits), aggregation pipelines (query_stats), and a UUID lookup convenience wrapper. However, search_logs, query_stats, hits, and search_uuid all run LogsQL-style queries over logs with overlapping capability, and only the detailed descriptions keep them apart.

Naming Consistency3/5

All names share the victorialogs. prefix and snake_case, which aids readability. But the internal pattern is mixed: search_stream/search_logs/search_uuid are verb_noun, list_streams is verb_noun, field_names/field_values are noun phrases, and hits/query_stats are bare noun/verb forms, so there is no single predictable convention.

Tool Count5/5

Eight tools is well-scoped for a log-search backend, with each tool covering a distinct capability (search, discovery, aggregation, counting, ID lookup) rather than redundant variants. Nothing feels padded or missing.

Completeness4/5

The surface covers stream discovery, field/value discovery, raw search, UUID lookup, time-bucketed counting, and aggregation, which is broad lifecycle coverage for querying logs. Minor gaps exist (no explicit schema/field-type or export/tail tool), but agents can work around them via LogsQL.