berserk-mcp
The berserk-mcp server provides an agent-facing interface to Berserk observability, letting LLMs answer complex questions through pre-verified KQL tools — no KQL authoring required.
Infrastructure Discovery & Inventory
List containers, services, hosts, and metrics with log/metric breakdowns
Map containers to host VMs, inspect live schema, and explore unknown data sources
Resource & Performance Monitoring
Top containers by CPU/memory, per-host CPU load and memory usage
Berserk query engine latency (p50/p95/p99)
Log & Error Analysis
Error counts grouped by service, recent log lines for a specific service
Run arbitrary KQL queries as an escape hatch (
search)
SRE Tools
Error rate over time, host headroom (CPU/memory side-by-side), ingest health, service health rollup, top repeated error messages
SOC Tools
High-severity logs, log volume spike detection, newly first-seen services, persistently repeated errors, full incident timeline for a service
Claude Code Observability (requires Claude Code logs shipped to Berserk)
Recent activity, session rollups, tool-use histogram, failed tool results, full-text search across message/tool bodies
Custom Query Persistence
Save verified KQL queries as named reusable tools, list saved queries, run them deterministically
Automated Source Onboarding
Queue new services/metrics for automated KQL generation via an LLM-powered parser factory (free/local → paid model ladder)
Check discovery job status; auto-generated query packs are validated before saving
Role-Based Access
Filter tool visibility and inject role-specific primers for SRE, SOC, Claude Code, or Ops roles
Auditing
Amendments log tracks all saved query creations and updates for full auditability
Allows posting summaries and change logs from automated telemetry discovery and query authoring workers to a Discord channel.
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@berserk-mcpshow top CPU consumers for the last hour"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
berserk-mcp
berserk-mcp is an MCP server. It lets an LLM answer Berserk observability questions. The LLM calls tools for this. The LLM does not write KQL by hand.
Why this matters: A raw query language makes a model guess. It guesses wrong table names, wrong field names, and broken aggregations. Each wrong guess costs you a retry. Every tool in berserk-mcp wraps one verified Kusto/KQL query. The model picks an intent — for example
top_cpu,errors_by_service, orsre_host_headroom. The query itself stays fixed. This fixed-query design is the whole point. It lets even small or cheap models answer observability questions reliably.
Works with Claude Desktop, Claude Code, and any MCP client. By default, berserk-mcp speaks MCP protocol version
2025-06-18over stdio (newline-delimited JSON-RPC 2.0). It implements every required method —initialize,notifications/initialized,ping,tools/list,tools/call— with strict envelope validation and adversarial regression tests. See Connect it to a client forclaude_desktop_config.jsonandclaude mcp addrecipes.MCP compatibility baseline. The stable default remains
2025-06-18stdio. Additive2026-07-28MCP features are available only when explicitly enabled withBERSERK_MCP_ENABLE_2026_07_28=1, so existing clients keep their legacy response shapes. See MCP 2026-07-28 adaptation baseline.Optional HTTP transport is closed by default. stdio remains the default. HTTP opens no listener unless explicitly enabled, defaults to loopback, and fails closed for remote bind unless auth, Host allowlisting, and CIDR allowlisting are configured. See MCP HTTP transport and reverse proxy deployment and .env.example.
Zero dependencies. berserk-mcp uses only the Python standard library. You do not
pip installanything beyond the package itself. (The optional LLM parser factory usesurllib. It still adds no third-party dependency.)Small and auditable. berserk-mcp is standard-library-only. Its focused modules cover the MCP server, parser generation, Claude analytics, AI FinOps, KQL validation, schema snapshots, secret redaction, and ingestion advice. You can read, audit, and vendor each module without pulling in a framework.
Cross-platform. berserk-mcp runs anywhere the
bzrkCLI runs, including Windows.Safe by construction. berserk-mcp uses fixed queries. It validates input on every free-text tool. It never calls
shell=True. The Berserk token never touches this code.Self-extending (new in 1.7). An optional parser factory detects new sources arriving in Berserk. It uses an LLM to author, execute-verify, and save KQL "query packs" for each new source. The design follows Microsoft Sentinel's ASIM parser AI agent. It tries cheap providers first, enforces hard runaway fail-safes, and never lets a generated query overwrite a human one.
⚠️ Disclaimer — please read
berserk-mcp is an unofficial, community-built project. The Berserk project and its maintainers do not sponsor, endorse, support, or affiliate with it. berserk-mcp talks to Berserk only through the public
bzrkCLI. It uses no internal API and no reverse engineering.berserk-mcp is provided as-is, with no warranty and no liability. This covers any use, outcome, downtime, data loss, or cost (see LICENSE). You run berserk-mcp at your own risk against your own infrastructure. Pointing it at a production Berserk is your decision.
For bugs, feature requests, and questions about this server: open an issue in this repository. For questions about Berserk itself: contact the Berserk project, not this repository.
Related MCP server: MCP KQL Server
Release history
Current version: 1.24.0. This is a bullet-point overview, most recent
first — full detail for each notable release lives in
docs/releases/.
v1.24.0 (2026-07-31) — MCP 2026-07-28 adaptation and safe-default HTTP transport: gated modern discovery, modern result envelopes, structured reporting output, private cache hints, input-required guidance, in-memory task lifecycle support, and a closed-by-default HTTP listener with auth, Host, CIDR, request-size, concurrency, and reverse-proxy guidance. See details.
v1.23.0 (2026-07-27) — Security remediation Phase 3: generated-content sanitization, per-deployment HMAC owner pseudonyms, spreadsheet-safe CSV, model-facing fence hardening, mandatory Discord egress redaction, scrubbed public deployment examples, and honest fixed-window Grafana dashboards. See details.
v1.22.0 (2026-07-27) — Security remediation Phases 0–2: execution-boundary KQL guards, bounded
bzrkoutput, trusted binary resolution, deterministic FinOps redaction, shared cross-platform private stores, hardened HTTP for every outbound caller, strict primer configuration, and offline regression coverage. See details.v1.21.1 (2026-07-27) — Bugfix:
claude_token_burn,claude_cost_report, and the AI FinOps usage pipeline were over-counting tokens/cost. Claude Code logs one JSONL row per content block of a response, each carrying an identical copy of that response's usage; nothing downstream collapsed them back to one billable call. Now grouped byclaude.message_idwherever tokens are summed, client-side and in KQL. See details.v1.21.0 (2026-07-25) — Adds enterprise Claude AI FinOps: native and legacy telemetry normalization, effective-dated API-equivalent pricing, governed feature/hour imports, deterministic attribution, management MCP reports, harness feedback, generated dashboards, seven BI datasets, and Grafana/Berserk dashboard assets. See details.
v1.20.0 (2026-07-24) — Adds schema-grounded, cost-aware KQL validation:
validate_kql, normalized schema snapshots, strict/warn modes, arbitrary search and saved-query validation, generated-query schema context, live runtime receipts, and a bounded in-process query concurrency guard. See details.v1.19.0 (2026-07-23) — Bugfix: the v1.18.0 interactive tool budget was a flat 10s regardless of window size; a real 72h query needing ~13s got rejected outright. Budget now scales with the requested window. See details.
v1.18.1 (2026-07-23) — Fixes a
windows-latest/Python 3.11+ CI failure:_FileLockdidn't catch a transientPermissionErrorWindows can raise instead ofFileExistsErrorunder lock contention. See details.v1.18.0 (2026-07-23) — Adds fleet-friendly worker jitter, interactive query budgets, timeout cooldowns, short-TTL read-only caching, and three guarded SRE/SOC tools:
detect_anomalies,forecast_capacity, andfind_similar. See details.v1.17.0 (2026-07-23) — Adopts native Berserk functions (
make-series,series_fit_line,fieldstats,tail,extract_log_template, …) across the query builders. See details.v1.16.0 (2026-07-23) — Berserk-native query optimization pass; adds the
claude.file_targets-based cost-attribution fix and the KQL performance guide. See details.v1.15.0 (2026-07-20) — Phase J deep analytics (
claude_cost_report,claude_session_deep_dive,claude_workflow_insights), a 9-finding security hardening pass, and the Discord alert integration. See details.v1.14.1 (2026-07-18) — Fixed a silent-failure bug in the agent-analytics tools'
bzrk --jsonparsing. See details.v1.14.0 (2026-07-17) — Distributed-trace analysis tools (
trace_find_slow,trace_find_errors,trace_analyze), shipped unverified then live-verified against a real cluster outage. See details.v1.12.0 (2026-07-15) — Agent-log analytics (
claude_loop_check,claude_model_fit,claude_token_burn), secret detection/redaction, and the ingestion advisor (suggest_ingestion). See details.v1.7.1 — Runaway fail-safes for source auto-detection.
v1.7.0 — LLM-driven parser factory for new Berserk sources.
v1.6.2 — Security review findings from the 2026-07-05 pass.
v1.6.0–1.6.1 — Role profiles (SRE/SOC/Claude/Ops), role primers, amendments logging, and early hardening fixes.
v1.2.0–1.5.0 — Initial release through discovery tools, dual-perspective
discover_schema, andbzrk_query_perf. Seegit logfor individual commits.
Why this exists
Berserk is a self-hosted, OTEL-native, schemaless observability engine. It
is built for petabyte scale. Berserk ingests logs, metrics, and traces over
OTLP. You query this data with a Kusto-style language (KQL), through the
bzrk CLI or the web UI. Berserk is headless by design:
built for "agents asking questions," not for dashboards. Berserk supplies the
storage and the query engine. On its own, Berserk still assumes the asker —
human or agent — already knows KQL.
The gap. A raw query language is the one thing LLMs handle badly. Point a
model at bzrk directly, and it invents table names, mistypes fields, and
burns tokens on retries. Two obvious fixes were tried first: pasting the
schema into the prompt, and few-shot KQL examples. Neither fix held — the
model kept guessing. Hardcoding the queries did work.
What berserk-mcp adds. berserk-mcp is a translation layer in front of
Berserk. It exposes observability intents as MCP tools — for example
top_cpu, errors_by_service, sre_service_health. Each tool wraps a query
already verified against the live schema. The model never authors KQL; it
picks an intent and a time window. berserk-mcp does not replace Berserk's
storage, query engine, or UI. It makes them agent-accessible and reliable
on small, cheap, or local models.
Beyond the fixed tools, berserk-mcp adds three layers that default Berserk does not have:
Role lanes — tool visibility filtered by job function, so each agent sees only the tools it needs
Discovery queue and auto-KQL worker — automated onboarding for new telemetry sources
Amendments log — every
save_querywrite is tracked, so a worker can post changelogs and keep the query store auditable
Approach | Result |
Berserk web UI / | Good for a human who knows KQL. Not usable by an agent. |
Point an LLM at the raw CLI and schema docs | Unreliable. Models guess table and field names, and pay for retries. |
A generic "text-to-KQL" MCP | Still authors queries. Same guessing problem, one layer up. |
berserk-mcp | Fixed, verified queries. Deterministic answers, even from a 7B local model. |
What this adds vs. default Berserk
Berserk is a strong human-facing observability backend on its own. berserk-mcp does not replace any of it. berserk-mcp sits next to Berserk and adds the agent-facing surface:
Capability | Default Berserk | berserk-mcp |
Ingest OTLP logs / metrics / traces | ✅ core | reuses |
KQL query engine + storage | ✅ core | reuses (read-only) |
Web UI + | ✅ core | reuses |
Token auth, profiles | ✅ core | reuses ( |
MCP surface for LLMs / agents | — | ✅ |
Common questions answered without authoring KQL | requires correct Kusto → small models fail | ✅ fixed verified tools |
Role-aware tool filtering (SRE / SOC / Claude / Ops lanes) | — | ✅ |
Role primers injected at | — | ✅ KQL rules, thresholds, routing guidance per lane |
Telemetry-shape discovery | partial ( | ✅ |
Custom-query persistence as named, reusable tools | UI has a Query Library. Berserk documents no API or CLI verb to create, list, or share a saved query programmatically | ✅ |
Automated source onboarding | — | ✅ |
LLM parser factory — detect a new source, auto-author + verify a KQL query pack | — | ✅ |
Query changelog / amendments log | — | ✅ every |
Two-lane cost model (cheap default · on-demand | — | ✅ tool descriptions + annotations make this safe |
KQL-injection guards on free-text inputs | n/a (humans) | ✅ service-name allowlist · |
Trace/span analysis — find slow/failed traces, reconstruct a span tree with correlated logs | — | ✅ |
Why this complements Berserk's native MCP (not competes with it)
Berserk ships its own MCP server, bzrk mcp. It is a raw query console:
query/start_query sessions, table/database discovery, and get_docs for
KQL reference. Its design bet is "the agent writes the KQL." That console is
the right substrate for a human-grade KQL author. It is the wrong everyday
interface for most models — models guess table names, botch aggregations,
and burn tokens on retries.
berserk-mcp is the deterministic interpretation layer on top of the same substrate. The model picks a verified intent. berserk-mcp does the math. The answer comes back as a conclusion — a verdict, a baseline deviation, a cost trend — not a row dump.
Use the native MCP when a human-grade KQL author drives the session. Use berserk-mcp when you want any model, including small local ones, to answer reliably. Both servers run side-by-side in the same client without conflict.
Sovereign and defense deployments (fully local stack)
Every layer of this stack can run on hardware you own, with zero cloud egress. This makes it suitable for sovereignty-constrained and defense or air-gapped environments:
Berserk is self-hosted. Telemetry never leaves your network.
berserk-mcp is pure Python stdlib. It has no third-party packages, no telemetry, and no phone-home. You can audit its five small files in an afternoon.
The LLM layer can run locally too. The parser factory's provider ladder speaks the OpenAI-compatible API. Any locally hosted open-weight model — via Ollama, llama.cpp, vLLM, or LM Studio — plugs in as the
hermesendpoint. No frontier API is required. The fixed-query design exists precisely so small local models route reliably: the model picks a tool and a time window; it never authors KQL.Defense-in-depth on the egress path. Even when an LLM endpoint is configured, it receives only structural telemetry — key names, shapes, redacted excerpts — never raw values. The endpoint URL is scheme-allowlisted and operator-controlled.
Bridging Berserk's two use cases: AI Ops without leaving the sovereign boundary
Berserk's own positioning splits into two cases. AI Ops says "any MCP-aware agent can query your telemetry directly." Defence says "nothing leaves the trust boundary you control." Taken separately, these two cases pull in opposite directions. The AI Ops case assumes a capable model that authors KQL and reasons over raw results. But a frontier model is itself an egress dependency, and the Defence case rules that out. Berserk-the-engine solves this for the data: self-hosted, WORM storage, no foreign jurisdiction. It does not solve this for the reasoning layer on top of the data.
berserk-mcp closes that gap. The model only ever picks a tool and a time window. It never authors KQL and never sees raw values. This means a small, locally hosted open-weight model can drive the whole interaction reliably. The result is the AI Ops experience — agents ask questions instead of humans reading dashboards — with the entire agent loop inside the sovereign boundary, not just the telemetry store.
This is not hypothetical. One production-like deployment uses a
Discord-facing local agent to answer on-call questions against Berserk. The
agent logs every tool call and every full prompt/reply back into Berserk
itself: model name, redacted arguments, redacted results, and session ID, all
as structured, queryable records. This is the same durable,
back-testable "what did the agent actually do" record that Berserk's AI Ops
page highlights in its Ethira governance case study — running end-to-end
against berserk-mcp instead of a bespoke integration. The claude_* tool
family (claude_cost_report, claude_token_burn, claude_workflow_insights,
and others) delivers the same token-usage/BI story from that page. These
tools are already implemented and already answering real queries.
The target operating model is two-tier local. A small open-weight model
handles the everyday calls; the goal is ≥ 80% of interactions. The model
escalates to a larger, locally hosted open-weight model only for @deep
work: parser generation, deep-dive synthesis, incident narratives. The
measurement plan for picking both tiers is in
evals/model-eval-plan.md (Part 3).
Architecture
How the lanes talk to each other and to Berserk
flowchart TB
classDef user fill:#0d1117,stroke:#58a6ff,color:#c9d1d9
classDef cheap fill:#0d3a1d,stroke:#3fb950,color:#c9d1d9
classDef deep fill:#3a1d0d,stroke:#d29922,color:#c9d1d9
classDef mcp fill:#161b22,stroke:#8b949e,color:#c9d1d9
classDef berserk fill:#1d1d3a,stroke:#a371f7,color:#c9d1d9
classDef store fill:#0d1117,stroke:#8b949e,color:#c9d1d9,stroke-dasharray:3 3
User([User · Slack bot · agent framework]):::user
subgraph H["MCP Host (Claude Code · Claude Desktop · LangChain · ChatOps bot)"]
direction TB
Cheap["⚡ DEFAULT lane\ncheap / local model\ngpt-4.1-mini · Qwen2.5-7B · Haiku\nonly picks tools + time windows"]:::cheap
Deep["🧠 @deep / scheduled lane\ncapable model\nsonnet · GPT-class\nauthors + verifies KQL"]:::deep
end
subgraph M["berserk-mcp (stdio · JSON-RPC 2.0 · zero-dep stdlib Python)"]
direction TB
RoleFilter["Role filter BERSERK_MCP_ROLE=sre|soc|claude|ops|all\ntools/list filtered at protocol level\nprimer injected at initialize"]:::mcp
Tools["Fixed tools — verified KQL\ntop_cpu · errors_by_service · host_cpu\nlogs_for_service · sre_* · soc_* · claude_*"]:::mcp
Disc["Discovery tools\nlist_metrics · discover_schema\ncontainer_hosts · list_services · schema"]:::mcp
Learn["Learning loop\nsearch → save_query → run_saved\nverify-before-persist · amendments log · 500 cap"]:::mcp
Queue[("discovery_queue.json\nknown_sources.json\namendments_log.json")]:::store
Store[("learned.json\n~/.config/berserk-mcp")]:::store
end
Worker["discover-worker\ndrains queue · authors KQL · posts Discord\nruns via daily cron"]:::deep
Bzrk["bzrk CLI\nbearer token lives only in bzrk's own config\nMCP never reads or stores it"]:::berserk
subgraph B["Your Berserk instance"]
direction TB
Gw["Berserk gateway · KQL engine"]:::berserk
Tbl[("default table\nOTLP logs · metrics · traces")]:::berserk
end
User -- "natural-language Q" --> Cheap
User -- "@deep prompt · once-a-day cron" --> Deep
Cheap -- "tools/call — role-filtered tools" --> RoleFilter
RoleFilter --> Tools
RoleFilter --> Disc
RoleFilter --> Learn
Deep -- "discover → search KQL → save_query" --> Learn
Deep -- "request_discovery" --> Queue
Queue --> Worker
Worker -- "save_query per source" --> Learn
Worker -- "Discord summary" --> User
Tools -. "argv list (no shell)" .-> Bzrk
Disc -. "argv list (no shell)" .-> Bzrk
Learn -. "verifies query before persist" .-> Bzrk
Learn <-->|persist · reuse| Store
Bzrk -- "read-only KQL over bearer auth" --> Gw
Gw --> Tbl
Learn -. "saved queries reusable by Cheap forever" .-> CheapThe diagram makes three things clear:
The bearer token never enters this code.
bzrkowns the token in its own configuration. berserk-mcp invokes it with an argv list: no shell, no token in berserk-mcp process memory, no token in berserk-mcp logs. Private-file permissions are platform-specific; see Security.The learning loop closes back into the cheap lane. Pay the capable model once to author and verify a query. After that, the cheap lane runs the query free, forever, via
run_saved.The worker is the automation bridge. When
request_discoveryqueues a new source, the worker drains the queue on its own — it discovers the source, authors KQL, and saves the query, with no operator KQL authoring.
Example ingestion topology (not shown in the diagram)
The diagram above covers the query path: how an agent asks questions.
The ingestion path is separate. A typical deployment runs a lightweight
journal forwarder on each monitored host. It tails explicitly selected
services and ships OTLP log payloads through a local collector into the
Berserk gateway. Each service uses its own
resource['service.name'], so list_services, logs_for_service, and
search filter by the workload rather than the forwarding mechanism. Keep
real host and service inventories in private deployment documentation.
Role lanes
Set BERSERK_MCP_ROLE to scope what an agent sees. The filter applies at the
MCP protocol level. An unrelated tool never appears in tools/list, so it
cannot be called by accident and cannot be injected into context.
Role |
| Gets | Typical agent |
SRE |
| Core tools + SRE tools (error rate, host headroom, ingest health, service health, top errors) | On-call Slack bot, editor assistant |
SOC |
| Core tools + SOC tools (high-severity logs, log spike, new services, repeated errors, incident timeline) | Security monitoring agent |
Claude Code |
| Core tools + Claude telemetry, AI spend, feature economics, data quality, and governed harness recommendations | Developer workflow and AI FinOps assistant |
Ops |
| All tools (full visibility) | Operator shell, admin scripts |
Default |
| All tools | Development, evaluation |
Role primers
When a lane connects, berserk-mcp injects a markdown primer into the MCP
initialize response, before the standard instructions. Each primer carries:
Tool routing table — which tool to reach for first, for each intent
Escalation thresholds — for example CPU load > 2.0, memory > 85%, error rate > 10/min, ingest lag > 30 s
KQL authoring rules — time window defaults, field name conventions, aggregation patterns
Discovery flow guidance — when to call
request_discoveryinstead of authoring ad-hoc KQL
This means the agent config needs no prompt engineering. The routing knowledge travels with berserk-mcp.
Primers live in primers/<role>.md, next to the server file. An explicit
BERSERK_MCP_PRIMERS_DIR must be absolute and contain a readable <role>.md
for the active lane; otherwise startup fails with a configuration error. The
all role receives no primer and routes from tool descriptions directly.
Tools
Core tools (all lanes)
Tool | What it answers |
| Containers currently sending metrics (with sample counts). |
| Containers ranked by CPU %. Use for container-specific questions; for host CPU use |
| Containers ranked by memory (MB). Use for container-specific questions; for host memory use |
| ERROR-level log counts grouped by service. |
| All services/sources, with log vs metric breakdown. |
| All hosts reporting telemetry, by record count. |
| Per-host CPU (1-minute load average). Default for ambiguous whole-machine CPU questions. |
| Per-host memory used (GB). Default for ambiguous whole-machine memory questions. |
| Which host/VM each container runs on (join key for container↔host questions). |
| Recent log lines for one service. |
| Live tables + column schema introspection. |
| Every metric name being ingested, with counts (discovery). |
| Field metadata (type, cardinality, representative values) via Berserk's native |
| Validate custom KQL before saving or running it. Static mode checks syntax shape, schema fields, bounds, and cost-risk without executing the query; live mode is opt-in and returns a runtime receipt when enabled. |
| Berserk query engine latency percentiles (p50/p95/p99 in µs). |
| Run arbitrary KQL (escape hatch). Static validation runs before execution in the default |
Every query tool takes an optional since argument ("15m ago", "1h ago",
"2d ago", …) with a sensible per-tool default.
Per-host vs. per-container: host_cpu and host_memory report per host. top_cpu and top_memory report per container. The tool descriptions cross-reference each other, so the model picks the right one. For an ambiguous whole-machine question — for example "what's hammering the server?" — always prefer the host tools.
SRE tools (sre lane only)
Tool | What it answers |
| Error log events by service grouped per minute — "is the error rate climbing?" |
| CPU load and memory by host — "which VM is saturated?" |
| Berserk ingest lag and dropped data — "is observability lagging?" |
| Full health summary for one named service: event volume, error count, log/metric split, last seen. |
| Most-repeated error messages by service — "what error should I investigate first?" |
| Statistical service-volume anomaly detection using zero-filled series. |
| Native trend fit for an allowlisted host gauge; refuses weak forecasts. |
SOC tools (soc lane only)
Tool | What it answers |
| Recent CRITICAL/FATAL log lines with service and message text. |
| Services with the largest minute-level log bursts — "anything spiking?" |
| Recently first-seen services and sources — "what is new?" |
| Error messages that repeat persistently — probes, loops, stuck processes. |
| Full incident timeline for one named service: timestamps, severity, metric names, message snippets. |
| Statistical service-volume anomaly detection using zero-filled series. |
| Meaning-based log search when semantic indexing is enabled. |
| Aggregate potential-secret counts by service/type with first-seen timestamps. Values are never returned. |
Claude Code tools (claude lane only)
If you ship Claude Code session logs into Berserk (service name claude-code), these
tools mine that data. See docs/claude-code.md for the pipeline.
Tool | What it answers |
| Recent Claude Code events — type, role, model, tool names, error flag. |
| Sessions rollup — event counts, first/last seen, assistant turns, tool turns, error count. |
| Tool-use histogram — how many times each tool (Bash, Edit, Read, …) was called. |
| Failed tool results with message snippets. |
| Full-text search across Claude Code message and tool bodies. |
| Flags sessions that repeat the same tool/target, retry the same error, or oscillate between calls. |
| Heuristic model-tier fit: frontier model on trivial work, or cheap model on complex/repetitive work. Not a billing statement. |
| Token burn per session and progress unit, using exact usage attributes when present and a labeled estimate otherwise. |
| Multi-day cost report: per-day burn with exact/estimated labels, per-model split, optional per-project attribution from file paths, and a burn-growing/flat/declining trend verdict backed by Berserk's native |
| One session's timeline: contiguous tool phases with error counts, activity gaps over 5 minutes, cumulative burn, and a loop verdict. |
| Cross-session patterns: most common tool sequences, error hotspots by tool+target, top-decile burn-per-target sessions. |
| Token classes, public API-equivalent spend, cache ratio, trends, attribution, and pricing coverage grouped by business or technical dimension. |
| Planned/actual developer hours and AI budget/spend, completion forecast, repositories, agents, harnesses, and delivery outcomes for one feature. |
| Feature and repository economics within one project, including unattributed spend and data-quality coverage. |
| Evidence for expensive models, operations, retries, loops, cache misses, context growth, and agent fan-out. |
| Deterministic, stable-ID harness amendments with confidence, risk, validation window, and rollback criteria. |
| Append-only approval, rejection, or deferral audit record. Owners use a deployment-scoped HMAC pseudonym; rationale is stored as a hash. |
| Matched before/after harness comparison with keep, rollback, no-change, or insufficient-evidence verdict. |
| Portfolio, project, or feature summary as readable Markdown plus versioned structured JSON. |
| Privacy-safe Markdown or self-contained HTML snapshot beneath the configured report directory. |
Agent-log intelligence
A read-only analytics layer for the claude lane (v1.12.0; see
release notes):
claude_loop_checkgroups tool calls by session. It reports the repetition ratio, the top repeated call, the error-retry count, and a verdict:healthy,some-repetition, orlikely-looping.claude_model_fitmaps model names to a coarse tier (frontier,mid,cheap). It compares that tier to a complexity proxy built from tool count, errors, duration, and loop signals.claude_token_burnusesclaude.tokens_inputandclaude.tokens_outputwhen present. When they are absent, it falls back per session tobody characters / 4. It computes burn per distinct tool plus inferred file target, and highlights top-decile burn. Every result labels its source as exact or estimated.--agent-reportruns all three checks headlessly. It exits non-zero when a session is likely looping or underpowered, so cron or systemd can pipe the stdout summary to an alert transport. "high-burn" alone is a relative marker — it is always present, because it is a top-decile ranking — so it is intentionally excluded from the alert threshold:
berserk-mcp --agent-report --since "6h ago"
berserk-mcp --agent-report --agent-report-mode weekly --agent-report-json --since "7d ago"Phase J deep analytics (v1.15.0; see release notes):
claude_cost_report, claude_session_deep_dive, and claude_workflow_insights
extend this layer with multi-day cost trends, per-session timeline
drilldowns, and cross-session workflow patterns. Per-project cost
attribution infers a project name from file-target paths: it uses the
directory before the first marker segment (src, tests, lib, pkg).
Override this with BERSERK_MCP_PROJECT_MARKERS.
claude_token_burn, claude_loop_check, and claude_model_fit parse real
bzrk --json output directly — _json_records() unwraps Tables[0].rows
against Tables[0].schema.columns, matching each row's positional array to
its column order. claude.tokens_input and claude.tokens_output are the
real attribute names used for exact token counts. See
the v1.14.1 release notes for the silent-failure
bug this fixed and the live-verification story behind it.
Enterprise AI development economics
Version 1.21 adds a canonical cost and attribution layer behind the newer Claude tools. Native Claude Code OpenTelemetry is the preferred input; the existing JSONL-forwarder attributes remain compatible. Reports normalize input, output, cache-read, cache-creation, long-context, and chargeable server-tool usage, then calculate a versioned public API-equivalent cost. Claude's reported approximate cost remains a separate field. This is not an invoice, and an unknown model is left unpriced rather than assigned a guessed rate.
Launch Claude with governed work context so telemetry can be attributed to a feature without inspecting prompts or source code:
berserk-claude --team platform --project OBS --feature OBS-142 \
--work-item ADO-912 --repository berserk-mcp --harness-version finops-v1 \
-- claudeImport planning and actual-hours data through the neutral CSV/NDJSON contract.
The local store is updated atomically; configure an OTLP logs endpoint to also
emit privacy-safe engineering-work records:
berserk-mcp --import-business-data feature --input /absolute/path/features.csv
berserk-mcp --import-business-data effort --input /absolute/path/worklogs.ndjson \
--input-format ndjsonFeature records use stable IDs plus optional portfolio, project, work-item, repository, branch, pull-request, planned-hours, AI-budget, and completion fields. Effort records contain a stable worklog ID, feature/work-item/team, work date, actual hours, source system, and update timestamp. Developer hours are never inferred from session duration, commits, or Claude active time.
Create management-ready outputs from the same model:
berserk-mcp --export-bi --since "90d ago" \
--output /absolute/path/ai-finops-export --export-format csv
berserk-mcp --generate-dashboard project --identifier OBS --since "90d ago" \
--dashboard-format htmlThe export contains seven versioned datasets and a checksum manifest. Grafana
provisioning JSON and bounded Berserk Explore queries are in
dashboards/. Generated snapshots contain aggregates,
coverage, pricing version, freshness metadata, and deployment-scoped owner
pseudonyms where management attribution is needed. They do not contain prompts,
code, raw commands, session bodies, cleartext owner IDs, or email addresses.
Harness amendments are recommendations only: an owner must record a decision,
deploy an immutable harness version, and use the matched-cohort impact tool
before keeping or rolling back a change.
Private local stores use current-user-only permissions. BI exports and generated management reports are publication outputs: berserk-mcp leaves an existing output directory's mode or ACL unchanged so a BI service account is not locked out. The operator owns access control for those publication directories.
Secret detection and output redaction
A stdlib-only secret scanner at the MCP output boundary (v1.12.0; see
release notes). BERSERK_MCP_REDACT controls
how every tools/call result is handled:
redact(default since F-009, 2026-07-20) replaces detected values with typed placeholders, such as[REDACTED:aws_key].flagleaves the result intact and prepends a warning when a secret is detected. This is an explicit opt-in away from the safer default. berserk-mcp logs a startup warning to stderr when you set this.offdisables output scanning entirely. This is also an explicit opt-in, with a startup warning.
An unrecognized BERSERK_MCP_REDACT value fails closed to redact, the
strictest mode, never to a weaker one.
The scanner recognizes common cloud/provider credentials, private keys,
JWTs, bearer tokens, and generic password/token assignments. High-entropy
matching is opt-in, because it is false-positive-prone. Email, IP, and
Luhn-validated credit-card checks are each individually selectable.
scan_secrets audits recent log bodies but returns only aggregate counts and
timestamps; it never returns the matched values. This protects MCP output
only. You must still remove secrets already stored in Berserk at ingest, and
rotate any exposed credentials.
Learning loop tools (all lanes)
Tool | What it answers / does |
| List saved queries visible to the current role. Check here before authoring new KQL. |
| Run a saved query by name — deterministic, no KQL authoring. |
| Verify a KQL query runs, then persist it under a name (with optional role tag). Logs every write to the amendments log. |
Ingestion advisor
suggest_ingestion is an all-lane read-only tool (v1.12.0; see
release notes), backed by the editable
ingestion_catalog.json knowledge base. The tool recommends concrete
sources, explains why each source matters, names an ingestion mechanism,
and labels its maturity: turnkey, collector-receiver,
bridge-required, or manual.
Seeded use cases:
sre/aws-cloud-nativesre/azuresre/onprem-ad-healthsoc/endpoint-identitychange-management/ansiblescom
Set check_gap=true to compare service and metric hints with the live
Berserk inventory. Each recommendation is marked present or missing, with
the matching signal or the exact ingestion action. For example:
suggest_ingestion role_or_usecase=sre/onprem-ad-health check_gap=trueThe AD path recommends Security, System, and Directory Service channels
through the OTel Collector windowseventlog receiver. The Ansible path uses
the community.general.opentelemetry callback. SCOM is explicitly
bridge-required: it needs a read-only REST/API or warehouse-SQL-to-OTLP
bridge. The advisor does not claim a native SCOM OTel receiver exists.
Discovery tools (all lanes)
Tool | What it does |
| Queue a newly-added service or metric for automated onboarding. Validates the source exists in Berserk before accepting. |
| List pending and completed discovery jobs. |
Trace tools (all lanes)
Tool | What it answers |
| Highest-duration root spans in the time window — "what's slow?" Entry point before |
| Spans whose status indicates an error — "which requests failed?" Entry point before |
| Full breakdown of one trace by |
Distributed-trace analysis (v1.14.0; see
release notes), following this table's
<signal>_name field convention (metric_name for metrics, body and
severity_text for logs). This feature was ported from a separate
TypeScript MCP prototype that explored the same problem space.
These tools are verified against a real Berserk cluster whose own internal
services are self-instrumented — service=query, service=gateway, and
service=ingest spans are real trace/span data, not synthetic test
fixtures (see Live-verified, not just unit-tested).
Two design points worth knowing:
durationis a dynamic-typed column. Berserk's KQL engine rejectssort by durationdirectly.trace_find_slowcasts it withtoint(duration)before sorting.Not every row sharing a
trace_idis a span. Other correlated telemetry — for example a log row — can carry the sametrace_id/span_idwith a nullspan_name.trace_analyzefilters toisnotnull(span_name), and sorts bystart_timeso parent spans order correctly before their children.
(Both were live bugs found while verifying this feature against a real cluster outage — see the release notes for the full story.)
Native analytics and graceful degradation
detect_anomalies and forecast_capacity (v1.18.0; see
release notes) use Berserk's native series
functions, returning compact arrays instead of exporting raw event windows.
Forecast responses include R² and slope; trends with R² below 0.6 or a
non-positive slope are explicitly reported as not forecastable rather than
inventing a ceiling date.
find_similar depends on semantic indexing and the similarto parser
feature. On clusters where that feature is unavailable, the tool does not
fail open or pretend exact matching is semantic — it explains the
limitation and directs the caller to search with an exact has term.
Self-extending: discovery and learning
The fixed tools cover known telemetry. For data with no tool yet — a log source you just started shipping — a two-stage loop extends berserk-mcp without hand-editing code. The cheap lane stays deterministic throughout.
Stage 1: Discovery queue
QUEUE request_discovery(service="haproxy") → validates source, queues job
WORKER discover-worker drains queue at 06:00 → authors KQL by role/kind
SAVE save_query (verify-before-persist) → permanent, named query
REUSE run_saved("sre_haproxy_service") → cheap model, free, foreverrequest_discovery does one check before it accepts a job: it calls
list_services (or list_metrics) to confirm the source is actually
visible in Berserk. An unknown source is rejected with a clear error, so the
queue never fills with phantom jobs.
The discover-worker (berserk-mcp --worker, invoked from a daily cron
entry — there is no separate discover-worker.py file) drains the queue:
Chooses the right KQL template per role.
sregets a health summary,socgets an incident timeline,claudegets a health rollup, andmetrickind gets a drilldown aggregation.Calls
save_queryto verify and persist the result.Updates
known_sources.jsonso the same source is never re-queued.Posts a summary of completed and failed jobs to Discord, if
BERSERK_DISCORD_ALERT_SECRETis configured (see below). This step is skipped when there is nothing noteworthy — no new sources found and no jobs drained — so a quiet day does not generate a daily ping.
Stage 2: @deep amendments and improvements
A capable model (@deep, a scheduled agent, or an operator) may improve or
correct an existing query via save_query. The generation pipeline may also
save a new query. Either way, berserk-mcp:
Tags the entry
action=generated(pipeline-authored),action=updated(a human save to an existing name), oraction=created(a human save to a new name).Appends a timestamped entry to
amendments_log.json, with the name, description, KQL preview, role, and action.Reads and formats a changelog on the next
--workerrun, if Discord alerting is configured (🤖 generated, ✏️ updated, ✨ created). It clears the log only if the post is confirmed — a transient Discord outage leaves the entries intact for the next run, instead of losing them.
This means the query store is auditable. Once Discord alerting is configured, every improvement made by an autonomous agent can be surfaced in a Discord channel automatically, with no operator action.
Configuring Discord alerts
berserk-mcp does not talk to Discord's API directly. No bot token and no webhook secret lives in this process. Instead, berserk-mcp posts to a small local HTTP bridge (loopback by default) that already knows how to reach your Discord channel:
Variable | Default | Purpose |
|
| The bridge's alert endpoint. |
| unset | Shared secret sent as |
The bridge must accept POST <url> with header X-Auth-Token: <secret> and
JSON body {"text": "..."}, and return 2xx on success. If the bridge runs on
a different host than berserk-mcp's --worker cron job, the same
loopback-only-by-default policy applies as for the LLM endpoint. Set
BERSERK_LLM_ALLOW_PLAINTEXT_REMOTE=1 to allow a non-loopback http:// URL,
or point at an https:// bridge instead. Prefer HTTPS for any bridge that is
not bound to loopback; the shared secret is sent as an HTTP header and should
not cross an unencrypted network. Alerts are sent only from the
headless --worker CLI path. Interactive MCP tool calls (for example
run_discovery_worker) already surface their result directly to the caller
and never post to Discord — this avoids duplicate, noisy notifications.
The intended division of labour is cost-efficient:
A capable model does the rare, hard part. It discovers the new shape, authors and verifies the query, and calls
save_query. Trigger it two ways: on a schedule (a daily job that checks the discovery queue), or on demand ("I just added HAProxy to Berserk — add support").The cheap model reaps the result. Every saved query is reusable for free, deterministically, via
run_saved. Authoring KQL is the one thing small models handle badly, so this step is gated behind the stronger model.save_queryverifies the query runs before persisting it, as a guardrail.
This design scales because learned queries live behind
list_saved/run_saved, not as first-class tools. You can learn dozens of
new sources without growing the routing surface that keeps the cheap model
reliable.
Parser factory: LLM-generated query packs
The problem it solves: A new service or log type starts shipping to
Berserk, and there is no tool for it yet. Normally a human notices, explores
the shape with discover_schema, hand-writes KQL, and calls save_query.
The parser factory automates that loop.
The design follows Microsoft's ASIM parser AI agent for Sentinel:
sample the source, generate KQL, validate by executing it, refine on failure
(capped at 5 cycles), then persist the survivors. Sentinel's agent produces
stored ASIM parser functions. Berserk has no stored functions, so the output
here is a query pack: 2–4 verified save_query entries per source (an
overview, an errors/timeline view, and metric aggregates where appropriate).
Each entry is reusable forever afterward via run_saved on the cheap lane.
How the pipeline maps to Sentinel's ASIM agent stages:
ASIM parser AI agent (Sentinel) | berserk-mcp parser factory |
Requirements gathering | Discovery job — source name, kind, role hint |
Sample source data ( |
|
Generate the KQL parser | LLM generates a JSON query pack from the profile |
Schema validation ( | Declared output columns checked against real query output |
Data validation ( | Query is executed; must return rows (window widened once before failing) |
Refinement loop (≤ 5 cycles) | Validator error fed back to the model, ≤ 5 attempts per provider |
Deploy / package | Persisted through the existing |
Summary report | Report stored on the discovery job; visible via |
Escalation ladder. Generation tries providers in order: free and local first, expensive only on failure.
hermes (local/free) → openai → anthropicEach provider gets up to 5 refinement attempts. The previous failure's validator error feeds back into the next prompt. A provider with no configuration (no API key) is skipped after one attempt, instead of burning the full 5.
Tools:
Tool | What it does |
| Scans Berserk for services and metrics never seen before, and optionally for schema drift on known ones (new attribute keys on an existing service). |
| Synchronously generates and verifies a query pack for one named source, right now. |
| Drains up to N pending discovery jobs through the pipeline. |
| Lists or inspects LLM-generated saved queries. Audit these before you trust them. |
What it produces. For a newly-detected haproxy service, one run turns
this discovery job:
generate_parser(service="haproxy", role_hint="sre")into a set of verified, source-prefixed saved queries. Only the queries that actually returned rows are kept:
haproxy_overview – event volume, log/metric split, last seen
haproxy_error_rate – ERROR lines per minute
haproxy_top_backends – requests grouped by backendEach entry is stored with generated_by: {provider, model, ts, job_source}.
Each is immediately runnable on the cheap lane via
run_saved name=haproxy_overview.
Safety. Generated KQL passes through the same _KQL_PREFIX_RE guard as
human input. berserk-mcp saves a generated query only if it executes
successfully against Berserk. A generated query never silently overwrites a
human-saved one; on a name collision, it saves as <name>_gen instead. Every
generated entry carries generated_by: {provider, model, ts, job_source},
so review_generated can audit it before anyone trusts it in production. See
SECURITY.md for the full threat model, including the
indirect-prompt-injection risk from log data fed into generation prompts.
Runaway fail-safes. Auto-discovery is deliberately bounded. It can never flood the queue or burn a pile of LLM tokens in one pass — a real cluster can have hundreds of metrics:
Internal metrics are never auto-queued.
detect_new_sourcesrecords them in the baseline, so they do not re-flag as "new." It only ever queues services — the assistant never needs a per-metric query pack.Per-run service cap. A single detect pass queues at most
MAX_AUTOQUEUE_PER_RUNnew services (default 5; override withBERSERK_MAX_AUTOQUEUE). Any remainder is picked up on later runs.Per-run drain cap.
run_discovery_workerand--workergenerate for at most a bounded number of jobs per invocation (--max-jobs, capped at 5). A large pending queue drains gradually, not all at once.Ephemeral-name filter. berserk-mcp skips service names with no letters — for example a bare PID, or a changing numeric ID emitted as
service.nameby a misconfigured source. Otherwise these names look "new" on every run and would queue a junk pack forever.
The first detect_new_sources run against a fresh Berserk seeds the
baseline and queues nothing. Everything looks new on day one, so this first
run records the current state as the "known" set, instead of generating
hundreds of packs.
Headless / cron mode. An MCP stdio server runs only while a client is attached. A separate CLI path handles unattended scheduling:
python3 berserk_mcp.py --worker --auto-queue --max-jobs 2 --check-driftThis command detects new sources, queues them, drains up to --max-jobs
pending jobs, and exits 0 (or 1 if any job needed human review). Example cron
line:
* * * * * cd /path/to/berserk-mcp && python3 berserk_mcp.py --worker --auto-queue --max-jobs 2 >> ~/.local/state/berserk-worker.log 2>&1The worker applies up to BERSERK_WORKER_JITTER_SECONDS of random startup
jitter, so the cron entry can run every minute without synchronizing many
tenants on a fixed minute.
Configuration (all optional; a provider with no key configured is skipped):
Variable | Default | Purpose |
|
| Provider order for generation. |
| — | Bearer token for the Hermes/Open WebUI endpoint. |
|
| Hermes chat-completions endpoint. Resolution order: this env var, then a local |
| unset | Set to |
| auto-discovered via | Hermes model id. |
| — | OpenAI API key. |
|
| OpenAI model. |
| — | Anthropic API key. |
|
| Anthropic model. |
|
| Per-LLM-call timeout, seconds. |
|
| Max new services a single |
No new pip dependencies. LLM calls use urllib.request from the standard
library, matching the rest of berserk-mcp's zero-dependency design.
Note for Berserk maintainers. This feature exists because Berserk has no stored-function or saved-view primitive that an agent can create programmatically. So "a parser for a source" is emulated as a bundle of verified saved queries in berserk-mcp's own store. If Berserk ever exposes a gateway RPC for stored KQL functions or server-side saved views (the ASIM parser equivalent), this pipeline could target that directly instead. The generated packs would then become first-class Berserk objects. Feedback on whether such a primitive exists or is planned is very welcome — see CONTRIBUTING.md.
Worked examples
Concrete prompts you can paste into any MCP-aware client. Each example shows the natural-language question, the tools the model calls, and the kind of answer you get. All of these work on the cheap default lane — no frontier model required.
ChatOps: "any errors in the last hour?" (SRE lane)
Have there been any errors in the last hour, and from which service?Calls
errors_by_service(since="1h ago"). The model replies with the per-service error count, or "no errors recorded" when the result is empty. On the SRE lane, the primer nudges the model towardsre_error_ratefor a time-series view when the count is above threshold.
On-call triage: "is api-gateway healthy?" (SRE lane)
Is api-gateway healthy? What's the error rate and when was it last seen?Calls
sre_service_health(service="api-gateway"). It returns total events, error count, log/metric split, and the last-seen timestamp in one round trip. If the error count is high, the primer's threshold guidance nudges the model to follow up withsre_top_error_messages.
SOC investigation: "what happened on otel-collector?" (SOC lane)
Reconstruct what happened with otel-collector over the last 2 hours.Calls
soc_timeline(service="otel-collector", since="2h ago"). It returns timestamped events with severity, metric names, and message snippets, ordered newest-first — a ready-made incident narrative, with no KQL authoring.
Security sweep: "anything new or anomalous?" (SOC lane)
Anything unusual in the last 30 minutes? Spikes, new sources, repeated errors?Calls
soc_log_spike,soc_new_services, andsoc_repeated_errorsin one pass. The SOC primer tells the model to scan all three before summarising.
Developer workflow: "what tools is Claude Code using?" (Claude lane)
What tools has Claude Code used most this week, and were there any errors?Calls
claude_tools(since="7d ago")andclaude_errors. This only works if you ship Claude Code session logs into Berserk via an OTLP forwarder — see docs/claude-code.md.
Onboarding a new source
I just added HAProxy logs to Berserk. Integrate it.(With
SOUL.mdor a system prompt configured.) The agent callsrequest_discovery(service="haproxy", role_hint="sre"). The discovery worker runs overnight. It authors and savessre_haproxy_service. The next morning,run_savedanswers HAProxy questions on the cheap lane, permanently.
Autonomous daily health digest (cron / scheduled agent)
You are an on-call assistant. Use the Berserk MCP to:
1) Check load per host (host_cpu, host_memory) over the last 6 hours.
2) Count errors per service over the last 24 hours (errors_by_service).
3) List the top 5 noisiest containers (top_memory).
Write a 10-line digest, flag anything anomalous, and stop.This is deterministic enough to run unattended overnight on
gpt-4.1-minior a local Qwen2.5-7B. Wire it to a cron job — the answer is short and parseable.
Requirements
Python 3.9+. (Python 3.8 reached upstream end-of-life on 2024-10-07 and is no longer a supported floor.)
The
bzrkCLI, installed and authenticated (bzrk -P <profile> search "..."must work). The bearer token lives inbzrk's own config. berserk-mcp never reads or stores it.
Install
berserk-mcp is not yet published to PyPI. Install from source:
git clone https://github.com/ssimonsen0202/berserk_mcp
cd berserk_mcp
pip install .pip install berserk-mcp, pipx install berserk-mcp, and uvx berserk-mcp
will work once this project is published under that name. Do not run them
yet: the name berserk-mcp is currently unclaimed on PyPI, so those
commands would silently succeed against whatever unrelated or malicious
package claims it first.
berserk-mcp uses only the Python standard library. It has no third-party
runtime dependencies. Installation must include the accompanying local
modules declared in pyproject.toml plus packaged data (primers/,
ingestion_catalog.json). Use pip install . or a built wheel. Do not copy
berserk_mcp.py alone.
Authenticate to bzrk
berserk-mcp does not talk to Berserk directly. It wraps the bzrk CLI.
Authentication is bzrk's job, not berserk-mcp's. The Berserk bearer
token lives only in bzrk's own config. berserk-mcp never reads it,
stores it, forwards it, or logs it.
Recommended one-time setup:
# 1. Log in to Berserk with the profile name you'll use from the MCP.
bzrk login # follow the prompt for endpoint + token
# or
bzrk -P prod login # log in to a specific named profile
# 2. Verify auth works with the same profile the MCP will use.
bzrk -P local search "default | take 1" --since "1h ago"
# 3. Point the MCP at that profile (or leave BZRK_PROFILE unset for `local`).
export BZRK_PROFILE=localProfiles. Berserk uses named profiles (local, prod, staging, and
others). You can point the MCP at a different tenant by changing one env
var. berserk-mcp reads BZRK_PROFILE and passes it to every bzrk
invocation as -P <profile>. In claude_desktop_config.json this looks
like "env": {"BZRK_PROFILE": "prod"} — see Connect it to a
client below.
Non-default bzrk binary. If bzrk is not on $PATH — for example, if
it is Homebrew-installed or lives in a per-repo .venv — set BZRK_BIN to
the full path. berserk-mcp invokes bzrk with an argument list, never
through a shell, so quoting is not a concern. On Windows, use an absolute path
to the trusted executable. A bare name that resolves inside the MCP client's
current working directory is rejected to prevent executable planting.
Auth failures at runtime. If bzrk returns an authentication error —
bad token, expired session, wrong profile — berserk-mcp returns this
constant string:
bzrk authentication failed; run `bzrk login` and retryFor authentication failures, berserk-mcp never propagates raw bzrk stderr,
tokens, or tenant identifiers to the caller. Other backend diagnostics can be
returned to the MCP caller, but they are bounded and pass through output
redaction; see Security for the full rationale.
Full bzrk auth options (SSO, service accounts, per-profile config) are
out of scope for this README. See the official Berserk CLI docs at
https://docs.bzrk.dev. berserk-mcp only requires that bzrk -P <profile> search "..." succeeds, from the same shell environment berserk-mcp will
run in.
Fleet-friendly operation
When many MCP instances share one Berserk cluster, berserk-mcp limits the load each instance contributes (v1.18.0; see release notes):
Worker mode adds randomized startup jitter, preventing synchronized cron bursts.
Interactive calls use a separate per-tool budget and return an actionable narrower-window message when the budget is exceeded.
Identical timeout retries are suppressed briefly to prevent retry storms.
Allowlisted read-only rollups use a short in-process cache. Cached results are marked
(cached, <age>s old); mutation, discovery, generation, and arbitrary-search tools are never cached.
These controls are per-process and can be disabled or tuned with the environment variables in the configuration table below. Each default is a measured value, not an arbitrary guess — see the v1.18.0 release notes for the evaluation evidence behind each number.
Configure
All configuration is via environment variables. All are optional:
Variable | Default | Purpose |
|
| Trusted path/name of the Berserk CLI binary. Prefer an absolute path; it is required if a bare Windows name resolves inside the current working directory. |
|
| The |
|
| Per-query timeout, seconds (worker and generation paths). |
|
| Maximum random startup delay for |
|
| Base per-query budget for interactive |
|
| Added to the base budget per hour of the query's |
|
| Suppress identical timeout retries within one MCP process; |
|
| TTL for allowlisted read-only rollup results; derived from the synthetic trace replay; |
|
| KQL validation policy: |
|
| Enables |
|
| Maximum in-process Berserk queries at once. |
|
| Maximum user-supplied KQL length accepted by validation. |
|
| Recommended maximum arbitrary-query result bound used by validation warnings. |
|
|
|
|
| Hard cap on captured successful |
|
| The Berserk table to query. |
| platform config dir | Where saved queries persist ( |
|
| Active role lane: |
| adjacent | Optional absolute directory containing |
|
| Output handling: |
| unset | Set to |
| unset | Comma-separated PII checks: |
|
| Enable high-entropy filtering for AI FinOps free text. Format-valid structural IDs remain stable; secrets and PII are always redacted. |
| generated private key | Optional deployment-scoped HMAC secret for owner pseudonyms. If unset, a random key is created as |
| adjacent catalog | Optional path to an alternate |
|
| Claude-Code attribute holding input tokens. Override this if your forwarder emits a different name — for example |
|
| Claude-Code attribute holding output tokens (see above). |
|
| Path segments that mark "inside a project" for |
| platform config dir | Absolute path to the private, atomically updated feature catalog and developer-effort store. New private directories are restricted to the current user; existing directory permissions are never changed. |
| platform config dir | Absolute path to the private harness-decision audit store. Owner and rationale values are hashed before persistence. |
| platform config | Absolute publication directory for generated Markdown/HTML dashboards. Snapshot filenames cannot contain path components. Its existing ACL/mode remains operator-owned. |
| packaged catalog | Absolute path to an alternate effective-dated pricing catalog. Unknown models remain unpriced. |
|
| Optional OTLP/HTTP logs endpoint for |
|
| Comma-separated |
|
| Optional HTTP transport. Disabled by default; stdio remains the normal Claude Desktop/Claude Code path. |
|
| HTTP bind address when HTTP is enabled. Keep loopback when using a local reverse proxy. |
| unset | Required to bind HTTP to a non-loopback address. Remote bind also requires auth, Host allowlist, and CIDR allowlist. |
| unset | Bearer token for HTTP requests. Required for non-loopback bind and recommended behind a reverse proxy. |
| unset | Exact Host header allowlist. Required for non-loopback bind; wildcards are intentionally unsupported. |
|
| Source IP/CIDR allowlist. Global allow-all CIDRs such as |
|
| Maximum HTTP JSON request body size. Oversized requests return HTTP 413. |
|
| Maximum concurrent HTTP requests admitted to MCP dispatch. Excess requests return HTTP 429. |
| unset | Trust |
| unset | CIDRs for reverse proxies whose forwarded client IP should be trusted. Required when forwarded-header mode is enabled. |
Parser-factory (LLM parser generation) has its own env vars — see Parser factory above.
Transport security guidance
Use HTTPS/TLS for every non-loopback endpoint that carries a token, API key, or telemetry payload:
BERSERK_LLM_HERMES_URLmay usehttp://localhostorhttp://127.0.0.1for a local model gateway. Non-loopbackhttp://is rejected unlessBERSERK_LLM_ALLOW_PLAINTEXT_REMOTE=1is explicitly set. Treat that flag as a temporary private-network exception, not an enterprise default.BERSERK_DISCORD_ALERT_URLfollows the same loopback/plaintext policy. Usehttps://when the bridge runs on another host, becauseBERSERK_DISCORD_ALERT_SECRETis sent asX-Auth-Token.BERSERK_MCP_OTLP_LOGS_ENDPOINTis stricter: non-loopback OTLP endpoints must use HTTPS. Plain HTTP is accepted only for loopback collectors.The optional HTTP MCP transport is disabled by default. If enabled, it binds to loopback unless
BERSERK_MCP_HTTP_ALLOW_REMOTE=1is explicitly set, and remote bind fails closed without bearer auth, Host allowlisting, and CIDR allowlisting. Put remote/shared deployments behind HTTPS/TLS; see MCP HTTP transport and reverse proxy deployment.The Berserk cluster endpoint itself is configured inside the
bzrkCLI profile viabzrk login. berserk-mcp shells out tobzrkand never reads the stored bearer token or profile URL, so operators must ensure the CLI profile points at an HTTPS Berserk endpoint in shared or production deployments.
Code enforcement already covers the endpoints owned by berserk-mcp: URL schemes are allowlisted, embedded credentials and control characters are rejected, redirects are not followed, response bodies are bounded, and remote OTLP requires HTTPS.
Connect it to a client
Compatibility. berserk-mcp implements MCP protocol version 2025-06-18
as a stdio server (newline-delimited JSON-RPC 2.0). All 63 registered tools
appear in the tools/list handshake, and each can be invoked via
tools/call. The stdio handshake path — including every required
lifecycle method (initialize, notifications/initialized, ping,
tools/list, tools/call) — has been externally exercised by two
independent scanners: Cisco AI Defense mcp-scanner and MCP-Shield. Both
scanners enumerated the full tool surface with no protocol errors. Every
method has adversarial regression coverage in the test suite. Any client
that speaks the same protocol version — Claude
Desktop, Claude Code, and third-party MCP clients — can drive berserk-mcp
with no server-side changes.
Claude Desktop
Add to claude_desktop_config.json (Settings → Developer → Edit Config):
{
"mcpServers": {
"berserk-q": {
"command": "berserk-mcp",
"env": {
"BZRK_PROFILE": "local",
"BERSERK_MCP_ROLE": "sre"
}
}
}
}If you didn't pip install it, point at the file instead:
{
"mcpServers": {
"berserk-q": {
"command": "python",
"args": ["/absolute/path/to/berserk_mcp.py"],
"env": {
"BZRK_PROFILE": "local",
"BERSERK_MCP_ROLE": "sre"
}
}
}
}Claude Code
claude mcp add berserk-q -- berserk-mcp
# or from source:
claude mcp add berserk-q -- python /absolute/path/to/berserk_mcp.pySet the role in your shell or .env:
BERSERK_MCP_ROLE=sre claude mcp add berserk-q -- berserk-mcpAny MCP client
Launch berserk-mcp (or python berserk_mcp.py) as a stdio MCP server. It speaks
newline-delimited JSON-RPC 2.0 over stdio, MCP protocol version 2025-06-18.
Auditing tool calls from an agent-framework client
Some MCP hosts keep a full per-run session transcript on disk, including
every tool call's arguments and result. One example is an agent framework
named "Hermes." (This Hermes is unrelated to this repo's own
BERSERK_LLM_HERMES_URL/HERMES_API_KEY provider settings, described
above — those configure berserk-mcp's own upstream chat-completions
client for generation, not an MCP host.)
scripts/hermes_tool_call_log.py walks that transcript store. It emits one
full-fidelity JSON line per tool call — model, arguments, result,
untruncated — filterable by MCP server name. Use it to confirm which model
actually drove a tool call, or pipe it into jq for ad-hoc auditing. MCP's
stdio transport does not expose the caller's model identity to the server,
so this script fills that gap without berserk-mcp needing to know it.
Choosing a model
The whole point of the fixed-query design is that the model never writes KQL. It only picks a tool and a time window. This collapses the capability bar: instead of "can author correct Kusto," a model only needs "can do basic tool-calling." That is what makes cheap and local models viable. Lead with the cheapest option that works:
Local (preferred). Any Ollama or LM-Studio model with solid tool-calling works: the Qwen2.5-Instruct family (7B is the sweet spot), Llama 3.1/3.3, or Mistral-Small. Tiny models (≤2B) and CPU-only prefill struggle with agentic tool-call loops. Prefer a GPU and ≥7B for unattended use.
Cheap API.
gpt-4.1-mini, Claude Haiku, or Gemini Flash give strong tool use at a fraction of frontier cost. Good for latency-sensitive ChatOps replies.Frontier models are rarely necessary. Save them for open-ended investigations that lean on
searchandsave_query.
The biggest reliability lever, regardless of model, is the tool descriptions. They are written to be narrow and unambiguous, so a small model routes correctly. Keep new tool descriptions that way.
Security
Injection and execution-boundary guards. Every value interpolated into KQL has a character allowlist, control-character rejection, and a length cap. Arbitrary KQL must start with the configured table. The final
bzrkboundary rejects semicolons anywhere (including string literals) and control commands in every validation mode, before a process is spawned.Schema-grounded KQL validation. User-originated arbitrary KQL is statically validated before
search,save_query,run_saved, and generated-query persistence. The defaultwarnmode preserves normal workflows while rejecting malformed queries and source-introducing operators (union,externaldata,evaluate,find, and operator-formsearch) and surfacing cost warnings.strictalso blocks high-risk arbitrary/generated queries. Static cost is an estimate; it cannot know current cluster load from other tenants.Schema cache stores shape, not telemetry. The normalized schema snapshot records columns, resource/attribute field names, bounded examples, supported idioms, and a
schema_hash. It excludes raw bodies and is safe to rebuild when missing or corrupt. Parser-factory prompts receive compact schema context and are instructed not to invent fields.Live validation receipts do not fabricate engine stats.
validate_kql mode=livemeasures wall time locally and parses CLI stats only when available. Rows, bytes, and engine fields remainnullwith an explicit warning when the CLI does not return a supported stats format.Local query concurrency guard. Interactive query execution is limited by
BERSERK_MCP_MAX_CONCURRENT_QUERIESafter request validation and beforebzrkexecution. Semaphore slots are released on success, timeout, and exception.Read-only by construction. Every tool is annotated (
readOnlyHint) and issues only read KQL. The sole exceptions aresave_query, which writes a local query file (never Berserk), andrequest_discovery, which writes a local queue file.No shell.
subprocessis always invoked with an argument list, nevershell=True. berserk-mcp contains noeval.No secrets in this code. The Berserk bearer token lives only in
bzrk's own config. berserk-mcp never reads, stores, or logs it.Fail-closed secret scanning.
scan_secretsrefuses to report "clean" on any parse failure or multi-table response. Either the response is fully decoded and every audit record is validated, or berserk-mcp returns a controlledSecret scan failederror with no content echo.Structural telemetry only to LLM providers. The generation pipeline sends only
bag_keys(...),has_body,has_metric, and length-capped, fully-redacted excerpts. Raw telemetry values are never sent to any provider.Resource-key tokens are allowlisted, not just capped.
bag_keys(resource)output feeds the generation prompt as attribute-name tokens. Each token must match[A-Za-z0-9._-]— the shape of a real OTel dotted key — or berserk-mcp drops it outright. This means an instruction-shaped or control-character key from a misbehaving telemetry producer never reaches the prompt or the persisted schema cache. The list is capped at 50 keys, 80 characters each.Backend diagnostics are redacted before they cross the LLM/report boundary. A non-auth
bzrkexecution failure — profiling, query validation, or generation retry — has its raw stdout/stderr redacted and length-capped before it enters a refinement prompt or a persisted worker report. The same fix point,_safe_diag_text, runs at every crossing, so none can be missed.Bounded generation resources. A single
time.monotonic()deadline (BERSERK_LLM_JOB_DEADLINE_SECONDS, default 300s) spans an entiregenerate_parser_forjob. This deadline covers profiling, model discovery, every provider call, query verification, and retries — not just each individual HTTP call's own timeout. LLM provider responses are read with a hard byte cap (MAX_PROVIDER_RESPONSE_BYTES, 2 MB), instead of an unboundedresp.read(). The refinement-attempt budget (MAX_TOTAL_ATTEMPTS, default 8) is one total across the whole provider ladder, notMAX_REFINEMENT_ATTEMPTSattempts per provider. Hermes model discovery is cached per endpoint for 5 minutes, rather than re-queried on every attempt. The generated-job report is genuinely bounded toREPORT_CAPcharacters: oversized list fields are trimmed first, and if a single field is still oversized, the report falls back to a capped-scalar skeleton.Bounded
bzrkoutput. stdout and stderr are drained concurrently whilebzrkruns. Successful stdout is capped byBERSERK_MCP_MAX_RESULT_BYTES(10 MiB by default); overflow kills and reaps the child and returns an actionable narrowing message. Non-zero diagnostics have a separate 100,000-character cap.Cap eviction preserves human entries. The learned-query store's 500-item cap evicts the oldest generated entry to make room for a new generated write. It never evicts a human entry. If the store is saturated with human entries, and there is nothing generated left to evict, the generated write is refused with a
ValueError. It never silently drops a human save, and it never silently drops the new write itself. A human save (viasave_query) can always make room for itself, including by evicting old generated entries first.Concurrency-safe store writes. Every JSON store uses the same validated, atomic-write primitives and portable advisory lock. Private files are
0600with process-created0700directories on POSIX; Windows receives a protected current-user-only DACL. Existing directories are never chmod'd or re-ACL'd. A lock older than 30 seconds is treated as abandoned. A process suspended past that threshold can therefore create a lost-update race; this accepted residual risk and its operational mitigation are recorded in SECURITY.md.Role visibility enforced at call time, not just list time.
tools/listandtools/calluse the same predicate. A tool hidden from the active role cannot be invoked directly by naming it, even though it never appeared intools/list. A role-hidden tool and a genuinely nonexistent tool get an identical response; berserk-mcp never confirms that a hidden tool's name exists.BERSERK_MCP_ROLEis validated at startup: an unrecognized role refuses to start, with a clear error, rather than silently degrading to "almost no tools visible."Output redaction defaults to fail-closed.
BERSERK_MCP_REDACTdefaults toredact, the strictest mode. An unrecognized value fails closed toredactrather than a weaker mode. Choosingflagoroffis still fully supported. Each is an explicit, visible opt-in, with a startup warning on stderr — never a silent default.FinOps redaction preserves governed joins. Secret and PII redaction always runs on dashboards and BI data. Format-valid recommendation, request, session, feature, project, work-item, agent, harness, schema-hash, and dedupe IDs remain byte-stable across MCP output, dashboards, and exports. Optional entropy filtering applies to free text and malformed identifiers, not blindly to valid join keys.
Owner identifiers are pseudonymised, not anonymised. Feature owners and recommendation-decision owners are converted to the same deployment-scoped HMAC pseudonym before persistence. Cleartext owner IDs do not enter BI exports or generated dashboards. The pseudonyms can still be personal data and require the same access and retention controls as the underlying management records.
All KQL rejects semicolons unconditionally. Berserk executes semicolon-separated multi-statement KQL. Both generated-query validation and the final search execution boundary therefore reject any semicolon, including one inside a quoted string literal.
A malformed LLM provider reply can't crash the generation pipeline. A provider can legitimately return
content: null— common for a tool-call-only reply._parse_generated_replyvalidates the type before use, so a null or non-text value fails just the one job instead of the whole--workerloop.BERSERK_MAX_AUTOQUEUEis clamped, not just parsed. A negative value clamps to 0. An absurdly large value is capped at a hard ceiling of 500. An unparseable value falls back to the documented default of 5. This enforces the flood-control invariant regardless of the input value.CI and repository hygiene. GitHub Actions steps are pinned to a full commit SHA, not a mutable tag. The workflow explicitly declares
permissions: contents: read. The checkout step setspersist-credentials: false, so the runner never retains a token past the job.setuptoolsis version-bounded (>=61,<90), instead of open-ended..gitignoreexcludes.env*files, private-key file extensions, generated stores and reports (learned.json,discovery_queue.json,known_sources.json,schema_knowledge.json,amendments_log.json,llm_config.json,pseudonym.key,schema_snapshot_*.json,ai_finops_business.json,ai_finops_recommendations.json,reports/,.snapshots/,manifest.json,evals/results/), lock files, and temp files.Bounded redaction.
redact()uses two explicit limits:MAX_REDACT_CHARS = 1_000_000andMAX_REDACT_CANDIDATES = 50_000. Its sort-merge-join pipeline fail-closes to[REDACTED:redaction_limit]on any bound violation. No partial original text is ever returned.Constant auth-failure messaging.
bzrkauth failures always return the constant string"bzrk authentication failed; run \bzrk login` and retry"`. In this auth-failure path, berserk-mcp never returns raw stderr, tokens, or tenant identifiers. Non-auth backend diagnostics are still caller-visible when useful, but they are bounded and subject to output redaction.JSON-RPC 2.0 strict envelope validation.
initializerequires a nonempty stringprotocolVersion, and object-typedcapabilitiesandclientInfo.pingandtools/listreject nonempty params. A notification sent as a request is rejected with-32600. An unexpected handler exception surfaces as-32603, instead of silently converting to anisError=trueresult.Generated-query policy. An LLM-generated query must start with
{table} | .... It must terminate with| take N, where1 ≤ N ≤ 50. It must fit within 2,000 characters. The policy check runs on a stripped copy of the KQL, with string literals and//comments removed, so operator text inside a quoted string or a comment cannot satisfy the check.Provider error scrubbing. An HTTP error from an LLM provider returns only
"HTTP <code>". Response bodies and exception messages are never propagated to the caller.LLM endpoint scheme allowlist and loopback-gated plaintext. The operator-configured LLM endpoint URL (via
--set-hermes-urlorBERSERK_LLM_HERMES_URL) is validated at both write time and call time. Onlyhttp://andhttps://schemes are accepted. Control characters and newline-injection variants are rejected before any request is made. This is defense-in-depth againstfile://,gopher://,ftp://, and request-smuggling attempts, even though the operator is inside the trust boundary. Plaintexthttp://is additionally rejected for any non-loopback host, unlessBERSERK_LLM_ALLOW_PLAINTEXT_REMOTE=1is explicitly set — by default, a bearer token should not cross the network unencrypted.No automatic redirect following on outbound HTTP. LLM providers, Hermes discovery, OTLP export, Discord alerts, and the eval harness all use the same no-redirect opener and bounded response reader. Credentials are never forwarded to a redirect target. URL and header validation also runs at each call boundary; remote OTLP requires HTTPS.
Store and primer path validation. Every filesystem path accepted from an environment variable or CLI output option must be absolute, traversal-free, and control-character-free. This includes all JSON stores, schema caches, reports, BI exports, and
BERSERK_MCP_PRIMERS_DIR. An explicitly configured primer directory that lacks the active role's primer fails startup rather than silently weakening instructions.
Note on output. A tool result reflects whatever your telemetry contains. If logs in Berserk hold sensitive values, logs_for_service and search can surface them. Redact at ingest — not here.
Security review and testing
The security-relevant surface has been through a multi-round audit and an external scanner pass:
Hand audit. This covered 15 findings — 7 blocking security/DR issues, plus 8 behavioral/documentation issues that were initially deferred and are now closed — each with an adversarial regression test. Internal review reports are not published in this repo; closure evidence lives in the test suite's regression coverage.
Differential re-review. This produced 6 additional findings (
FVR-001throughFVR-006). All 6 are closed, with regression tests in the suite.External scanner pass. Three scanners ran against the code: Cisco AI Defense
mcp-scanner(YARA stdio and pip-audit), MCP-Shield (wire-level tool description keyword scan), and Snyk Code (SAST on the GitHub source). All three returned 0 true findings. MCP-Shield produced 3 keyword false positives on domain vocabulary (documented). Snyk produced two operator-controlled-input taint findings: CWE-918 SSRF on the LLM endpoint URL, and CWE-23 path traversal on the store-path env vars. Both are false positives by threat model, because the operator is inside the trust boundary — but both are addressed anyway, with defense-in-depth scheme and path allowlists.Ongoing verification. The test suite (
tests/, including role isolation coverage) includes adversarial regression coverage for the security findings above. It runs before every release. See## Testingbelow.
To report a vulnerability, see SECURITY.md.
Testing
python -m pytest tests/ -q
# stdlib unittest is also supported:
python3 -m unittest discover -s testsThe tests stub the bzrk CLI. They verify: KQL content and lock strings,
default time windows, role isolation (which tools appear in which lane),
injection guards, since validation, tool annotations, JSON-RPC protocol,
the learning loop, discovery-queue deduplication, and amendments-log
behavior. The parser-factory suite additionally fakes the LLM HTTP layer, to
verify the escalation ladder, source profiling, new-source/drift detection,
generation, validation, refinement, and headless worker mode. The
agent-analytics suite verifies loop detection, model-fit classification, MCP
dispatch, and the headless --agent-report path.
Live-verified, not just unit-tested
The stubbed suite proves berserk-mcp's logic is internally consistent. It does not prove the KQL executes correctly against a real cluster. So every SRE and SOC tool also runs through berserk-mcp's real dispatch path against a live Berserk deployment, as part of the release process. This live pass confirms, among other things:
soc_new_servicesuses a24h agodefault window with a shard-field filter, returning full results in about 28 seconds against real data volume.sre_host_headroomreports memory in GB, with an explicitunitcolumn distinguishing it from the CPU load-average rows — matchinghost_memory's units.The
trace_*tools (v1.14.0) sort correctly:trace_find_slowcastsdurationto an integer before sorting, andtrace_analyzeorders spans bystart_time. See Trace tools above.claude_token_burn,claude_loop_check, andclaude_model_fit(v1.14.1) parse realbzrk --jsonoutput correctly, including itsTables[0].rows/Tables[0].schema.columnsshape. See Agent-log intelligence above.
Extending — add a new tool in five minutes
The whole point of berserk-mcp is fixed, verified queries. Adding a tool is
a small, mechanical ritual. Aim to keep the routing surface small (about 20
core tools), and let the long tail accumulate behind save_query/run_saved
via the learning loop.
Before writing KQL, read the Berserk KQL performance guide.
It covers index-friendly predicates, tail for recency, narrow projections,
explicit limits, live verification, and the shared-cluster fleet rules. As of
v1.17.0, the guide's "Verified function availability" table also confirms
make-series, series_fit_line, series_decompose_anomalies, series_fir,
rate, deriv, bin_auto, extract_log_template, and fieldstats all work
against the live cluster — every core query builder now prefers these native
forms over hand-rolled bin()/sort/bag_keys equivalents where one exists.
1. Find the KQL on a live instance. Iterate with bzrk until the query
returns clean rows — names, units, sort order. Do not ship a query you have
not seen succeed against real data.
bzrk -P local search "default | where metric_name == 'system.network.io' \
| summarize bytes=sum(value) by host=tostring(resource['host.name'])" \
--since "1h ago"2. Add the tool entry:
TOOLS.append({
"name": "host_network",
"roles": ["sre"], # omit to make visible to all lanes
"description": "Total network bytes (sum) per host. Per-HOST; for per-container "
"network use `search` for now.",
"inputSchema": {"type": "object", "properties": _since()},
})
TITLES["host_network"] = "Per-Host Network I/O"Wire it to the dispatcher (fixed cmd key), and add a KQL constant for the
test.
3. Lock the query string with a test:
def test_q_host_net_locked(self):
self.assertIn("system.network.io", bm.Q_HOST_NET)4. Run the suite and re-register:
python -m pytest tests/ -q
claude mcp remove berserk-q && claude mcp add berserk-q -- berserk-mcpA tool that touches free-text input (a service name) needs an allowlist —
see logs_for_service. A tool that needs two bzrk round-trips can follow
discover_schema's pattern. Both patterns are in the source, as templates.
Contributing
Issues, ideas, and PRs are all welcome. See CONTRIBUTING.md for the short version. The bar is low: if the tests pass, the description is narrow, and the query has been seen working against real data, it is mergeable.
Good first contributions:
A new fixed-query tool for telemetry you actually care about
A worked example for your stack (Kubernetes, ECS, Nomad, and others) under docs/
Sharpening a tool description that confused your model. The descriptions are the router — a clearer one is a real correctness improvement.
Filing an issue when you hit something berserk-mcp should have a tool for
License
MIT.
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Servers
- AlicenseBqualityDmaintenanceEnables natural language exploration of Azure environments by generating and executing KQL queries against Azure Resource Graph. Supports multi-tenant configurations, subscription scoping, and provides direct access to Azure resource information through conversational interactions.82Apache 2.0
- Alicense-qualityBmaintenanceEnables intelligent KQL query execution against Azure Data Explorer clusters with AI-powered schema caching and natural language to KQL conversion. Provides automated schema discovery and context-aware query assistance for enhanced data exploration.23MIT
- AlicenseAqualityDmaintenanceEnables LLMs to query telemetry data via the Spyglass AI agent, providing intelligent insights about application performance, errors, and bottlenecks.1MIT
- Alicense-qualityDmaintenanceEnables natural language querying and analysis of OpenTelemetry traces, metrics, and logs stored in Elasticsearch/OpenSearch, allowing AI assistants to investigate performance issues, find root causes, and explore system behavior.1013MIT
Related MCP Connectors
Runtime permission, approval, and audit layer for AI agent tool execution.
The grounded data layer for any LLM: governed SQL, metrics, lineage and catalog over your data.
See, price, and control every tool call your AI agents make: policy checks, cost, and audit tools.
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/ssimonsen0202/berserk_mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server