LogLens
This server does not currently have a deployable release on Glama. Follow the repository's setup instructions to install and configure it for your MCP client, including any required credentials and local dependencies.
LogLens
An MCP (Model Context Protocol) server for log analysis. Instead of pasting a log file into a chat and asking an LLM to debug it, LogLens exposes log search, context retrieval, and incident summarization as tools that any MCP-compatible client (Claude Desktop, Claude Code, or your own agent) can call directly — with targeted retrieval instead of context-stuffing, and a self-verification step to catch unsupported root-cause claims before they're returned.
Why this exists
Built as a public, portfolio version of an AI-powered log analyzer that won 1st place at an internal hackathon. Short version of why this beats paste-into-chat: production logs don't fit in a context window, a chat-paste isn't callable by other systems, and raw prompting has no mechanism to check whether the model's answer is actually grounded in the log data.
Related MCP server: Log Analyzer MCP
Status
Core server, 3 tools — working end-to-end against a sample log. ✅
LLM-based root-cause generation with a hallucination-verification loop that checks the hypothesis on two independent axes and retries once if it fails either. ✅
Mixed-provider architecture (Groq + Gemini) — the verifier runs on a different model family than the generator, so the check doesn't share the generator's blind spots. ✅
8-case eval suite, deterministic scoring, 8/8 passing. ✅
Dockerized, runs as a network-reachable HTTP server, verified end-to-end against a live container. ✅
Deployed on Azure Container Apps. ✅ Live: https://loglens.livelymushroom-3f0e315d.centralindia.azurecontainerapps.io
Next: a demo GIF.
Tools
Tool | What it does |
| Keyword search over the log file; returns matches with surrounding context and a line |
| Given a line |
| Extracts search terms from a natural-language question, retrieves evidence (lexical + time-window + global-anomaly expansion), generates a root-cause hypothesis, then independently verifies it on two axes — retrying once if the verifier rejects it. |
Architecture
Question ──▶ extract search terms (Groq, gpt-oss-20b)
│
▼
search_logs (lexical match)
│
▼
+ time-window expansion (asymmetric: 900s before / 180s after —
causes precede symptoms)
│
▼
+ global anomaly scan (all WARN/ERROR lines, not just in-window —
the explaining line is often itself a warning)
│
▼
generate hypothesis (Groq, gpt-oss-120b)
│
▼
verify: soundness + completeness (Gemini — DIFFERENT provider
from the generator, on purpose; falls back to same-provider
Groq if Gemini is unavailable, and reports which happened)
│
unsound/incomplete? ──▶ regenerate once, feeding back
│ the lines the first pass overlooked
▼
answerTwo providers, deliberately — not just a cost workaround
Groq carries extraction and hypothesis generation; Gemini carries
verification. This started as a quota workaround (Gemini's free tier caps at
20 requests/day; Groq's is far more generous) but turned into a real
architectural improvement: a verifier running on the same model that
produced a claim shares that model's blind spots. Checking the claim with a
different model family makes the hallucination check genuinely independent,
not just a second opinion from the same source. Verification.independent
reports whether a given answer actually got the cross-provider check or fell
back to same-provider (Gemini down/unconfigured) — surfaced, not hidden.
The cross-service retrieval gap — found via testing, fixed at the right layer
Early testing surfaced a real limitation: summarize_incident found the
immediate cause of a checkout failure (DB pool exhaustion) but missed the
upstream cause the sample log also encodes — a long-running query on a
different service holding the connection. Two structural problems:
Retrieval was purely lexical. Extracted terms were checkout-scoped, so
inventory-servicelines could never enter the evidence set no matter how good the reasoning was. Fixed with time-window expansion, asymmetric on purpose (900s before / 180s after) — causes precede symptoms, often by more than a short symmetric window would catch — plus a global scan for anomalous (WARN/ERROR) lines regardless of window, since the explaining line is often itself a warning ("NTP sync failed", "rotation skipped").The verifier could only rubber-stamp. It originally saw only the lines a claim cited, which made it structurally incapable of noticing an incomplete answer — a claim describing a symptom will always look supported by the lines it chose to cite. It now sees the full evidence set and scores
soundnessandcompletenessindependently; a sound-but- incomplete verdict feeds the overlooked lines back into regeneration.
Eval suite
npx tsx evals/run-evals.ts # all 8 cases
npx tsx evals/run-evals.ts 03 08 # a subset, by id substring8 cases spanning distinct failure archetypes: cross-service resource contention, unbounded-cache OOM, retry-storm amplification, a bad deploy, two compounding causes, a single-bad-node clock skew, a healthy log (correct answer is "nothing failed"), and a loud-symptom-masking-subtle-cause case built specifically to exercise the completeness axis. Scoring is deterministic — concept groups with synonyms, plus required evidence citations, no LLM judge — so runs are reproducible. The report separates retrieval misses (evidence never reached the model) from reasoning misses (evidence was there, answer still wrong), since those need different fixes.
Current result: 8/8 passing, 0 retrieval misses, 0 reasoning misses.
Debugging this suite is itself a decent engineering story: an asymmetric
time window and a global anomaly scan fixed real retrieval brittleness; on
Groq's free tier, max_tokens is a reservation against a per-minute token
budget, not a pay-for-what-you-use ceiling — an oversized value gets a 413
regardless of actual prompt size; reasoning_effort: "low" was needed on the
gpt-oss models, which otherwise spend the budget on reasoning tokens and
truncate before emitting valid JSON; and the harness itself had two scoring
bugs (Unicode punctuation variants, then a plain-space variant of the same
compound identifier) that reported correct answers as failures — worth
knowing when you write your own eval harness: it needs debugging too.
Docker
docker build -t loglens:local .
docker run -d -p 3000:3000 \
-e GROQ_API_KEY=your-key \
-e GEMINI_API_KEY=your-key \
loglens:local
curl http://localhost:3000/healthMulti-stage build (compile with devDependencies, run with production-only
deps + a non-root user + a container healthcheck on /health). The
container runs the HTTP transport (MCP_TRANSPORT=http, set by default in
the image) rather than stdio, since a deployed container has no parent
process to spawn it locally the way Claude Desktop/Code do.
Real bug found and fixed while wiring this up, worth knowing if you build
your own stateless streamable-HTTP MCP server: the SDK's stateless mode
requires a fresh transport per request — reusing one transport across
requests silently 500s every request after the first, with no thrown
exception to catch. Separately, a single McpServer can only be connected
to one transport at a time ("Already connected to a transport"). The fix
(see createServer() and the HTTP handler in src/index.ts) creates both a
fresh McpServer and a fresh StreamableHTTPServerTransport per request —
cheap, since the server only holds tool definitions, no per-connection
state (none of these tools carry state between calls anyway). Verified
against a real running container: search_logs and a full summarize_incident
pass both completed correctly end-to-end after the fix.
Deployment
Live: https://loglens.livelymushroom-3f0e315d.centralindia.azurecontainerapps.io
Deployed on Azure Container Apps (Consumption plan). The image is pushed to Docker Hub (public — nothing sensitive is baked into it; secrets are injected at runtime, not build time) rather than Azure Container Registry, which avoids ACR's ~$5/month Basic-tier cost entirely:
docker tag loglens:local <dockerhub-user>/loglens:latest
docker push <dockerhub-user>/loglens:latest
az group create --name loglens-rg --location centralindia
az containerapp env create --name loglens-env --resource-group loglens-rg --location centralindia
az containerapp create \
--name loglens \
--resource-group loglens-rg \
--environment loglens-env \
--image docker.io/<dockerhub-user>/loglens:latest \
--target-port 3000 \
--ingress external \
--min-replicas 0 --max-replicas 1 \
--cpu 0.25 --memory 0.5Gi \
--secrets groq-api-key=<key> gemini-api-key=<key> \
--env-vars GROQ_API_KEY=secretref:groq-api-key GEMINI_API_KEY=secretref:gemini-api-key--min-replicas 0 is deliberate, not a default left alone. Azure's
"Always Free" grant (180K vCPU-seconds + 2M requests/month) is
usage-metered, not time-based — an always-on replica at even the smallest
size (0.25 vCPU) burns through that grant in about 8 days of continuous
uptime, then starts drawing on the temporary $200 trial credit instead of
staying free indefinitely. Scale-to-zero means billing only accrues on
actual requests, which is what keeps a low-traffic demo project genuinely
free long-term rather than free for 30 days. Tradeoff: a request after idle
time takes a few seconds to cold-start a replica.
Verified end-to-end against the live deployment (not just a health check):
tool listing and a real search_logs call both returned correct results
through the public URL.
Access control — public ingress needs it
--ingress external means the URL is reachable by anyone on the internet.
summarize_incident spends the deployer's own Groq/Gemini quota on every
call regardless of who's asking, not the caller's — so an unprotected public
endpoint means anyone who finds the URL can drain that quota. The data
itself isn't sensitive (a synthetic sample log), so this is a cost/
availability risk, not a privacy one — but real enough that it needed a real
fix rather than being left open by accident.
Two options were available: a hard auth gate (shared-secret header,
implemented in src/index.ts and available via LOGLENS_ACCESS_TOKEN for a
private deployment), or open access protected by rate limiting so anyone
can try the live demo without a handoff step. The public instance runs
the second — appropriate for a demo link shared with recruiters/
interviewers where a token exchange would be friction, not security.
src/rateLimit.ts enforces two independent limits, checked before a
request reaches the LLM pipeline at all, and scoped to summarize_incident
specifically — search_logs/get_error_context cost nothing and stay
unlimited:
Per-IP: 3 calls/hour, stops one source (or a runaway script) from hogging it.
Global daily cap: 15 calls/day across all visitors combined. This matters more than it might look — Gemini's free tier caps at 20 requests/day total, shared regardless of how many distinct people show up, so a per-IP limit alone wouldn't protect it; ten different visitors making two calls each would still exhaust it.
In-memory, deliberately — this runs at --max-replicas 1, and scale-to-zero
already resets in-memory state on a cold start, so a persistent store would
be false precision for a single-instance demo.
Verified against the live deployment: search_logs succeeds with no header
at all; 4 concurrent summarize_incident calls from the same source
returned exactly 3× 200 and 1× 429, matching the per-IP limit exactly.
A real Azure CLI gotcha hit while wiring this up, worth knowing:
az containerapp update --set-env-vars does not fully replace the env var
list the way "set" implies — omitting a previously-set key does not remove
it, and using the mutable :latest image tag meant Azure didn't detect a
change worth deploying at all (no new revision was created) until
--revision-suffix forced one explicitly. Removing an env var requires
setting it to an explicit empty value (KEY=""), not omitting it.
Environment variables
Variable | Required for | Notes |
|
| Get one free at console.groq.com/keys. |
| independent verification | Get one free at aistudio.google.com/apikey. Without it, verification falls back to same-provider (Groq) and is no longer independent — reported via |
| optional | Point at a real log file instead of the bundled sample. |
| optional, alternative to rate limiting | Requires every |
| optional |
|
| optional | HTTP port, default |
Setup
npm install
npm run buildBy default the server reads fixtures/sample.log, a synthetic incident
(a long-running unindexed query on inventory-service exhausts a shared DB
connection pool, cascading into checkout-service failures). Point it at a
real log file instead with:
LOGLENS_LOG_FILE=/path/to/real.log node dist/index.jsSmoke tests (no MCP client needed)
npx tsx scripts/smoke-test.ts # stdio transport
npx tsx scripts/smoke-test-http.ts http://localhost:3000/mcp # HTTP transportSpawns (or connects to) the server and calls all three tools — useful for
verifying it works before wiring up a real client. A full summarize_incident
pass is 3-4 sequential LLM calls and can take 30-90s; pass a generous
timeout if calling it programmatically (both scripts do).
Connect to Claude Desktop
Edit %APPDATA%\Claude\claude_desktop_config.json (Windows) and add:
{
"mcpServers": {
"loglens": {
"command": "node",
"args": ["C:\\Users\\sarve\\OneDrive\\Desktop\\LogLens\\dist\\index.js"],
"env": {
"GROQ_API_KEY": "your-groq-key",
"GEMINI_API_KEY": "your-gemini-key"
}
}
}
}The env block is required, not optional — MCP clients spawn the server with a sanitized environment by default, not your shell's full environment, so the keys won't be visible to summarize_incident without it even if set globally on your machine.
Restart Claude Desktop, then ask it something like "search the logs for
'pool exhausted'" — it should call search_logs automatically.
Connect to Claude Code
claude mcp add loglens --scope user --env GROQ_API_KEY=your-groq-key --env GEMINI_API_KEY=your-gemini-key -- node C:\Users\sarve\OneDrive\Desktop\LogLens\dist\index.js(Same reason as above — --env passes the keys explicitly since the spawned process doesn't inherit your shell environment by default.)
Project structure
src/
index.ts MCP server (dual transport: stdio + HTTP) + tool registration
logParser.ts log loading, search, time-window expansion, anomaly scan
summarize.ts the summarize_incident pipeline: extract -> retrieve -> hypothesize -> verify -> retry
providers.ts Groq + Gemini clients, model config, schema-constrained JSON generation
fixtures/
sample.log synthetic incident for local testing
evals/
cases.ts 8 eval case definitions
run-evals.ts deterministic scoring harness
logs/ synthetic logs for eval cases 02-08
scripts/
smoke-test.ts stdio transport smoke test
smoke-test-http.ts HTTP transport smoke test
Dockerfile multi-stage build, non-root user, container healthcheckThis server cannot be deployed
Maintenance
Related MCP Connectors
Analytics and debugging for your MCP server — explore usage and sessions, then root-cause errors.
Search log events, investigate anomalies, and manage cases in your Knowledge Grid tenant.
Read-only access to Auralogs production logs: search logs, inspect errors, review AI analyses.
Query application logs, traces, and metrics from your AI coding assistant via Foam's MCP server.
Related MCP Servers
- -licenseNot gradedqualityNot gradedmaintenanceProvides comprehensive logging and monitoring capabilities for MCP services with real-time log tailing, advanced search, error analysis, and anomaly detection. Enables centralized log aggregation, correlation tracking, and health monitoring across all MCP ecosystem services.-
- FlicenseBqualityCmaintenanceEnables AI-assisted analysis of log files through advanced searching, filtering, and test execution capabilities. Supports time-based queries, pattern matching, test summarization, and code coverage reporting directly within compatible MCP clients.12-
- FlicenseNot gradedqualityDmaintenanceEnables diagnosis of Google Cloud Platform logs using Gemini AI via MCP tools, fetching logs from Cloud Logging for issue analysis and root cause identification.-
- AlicenseNot gradedqualityDmaintenanceEnables LLMs to autonomously query AWS CloudWatch Logs and perform structured root-cause analysis via natural language prompts, using MCP tools for log group listing and Insights queries.MIT