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