lockwood-group-observability-mcp
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., "@lockwood-group-observability-mcpCluster the attached logs and highlight the most critical errors."
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.
lockwood-group-observability-mcp
Semantic log clustering for incident triage, exposed as an MCP server.
What it does and why
During an incident, the log stream is mostly repetition: the same failure printed thousands of times with different ids, plus a few distinct signals buried in the noise. This server takes a window of log lines and groups them by semantic similarity, then ranks the groups so the ones that matter for triage surface first. Instead of scrolling raw logs, an operator (or an agent driving the MCP tool) sees "3 clusters: a CRITICAL checkout 500 seen 240x, an ERROR payment timeout seen 1900x, ...".
Triage runs on the incident path, so latency is a hard requirement, not a
nice-to-have: a triage_logs call has a p99 budget under 150ms. That
budget shapes the whole design (see Architecture and docs/adr/).
Related MCP server: log-mcp
Architecture
inkwellobservability/
types.py domain types: LogRecord, Cluster, TriageResult, Severity
clustering.py TriageEngine + normalize_message (the core algorithm)
config.py env-driven Config with startup validation
errors.py ValidationError / ResourceLimitError / ConfigError
logging_setup.py JSON-lines logging to stderr
service.py argument validation + engine orchestration
server.py MCP JSON-RPC 2.0 server over stdio
__main__.py entry point (python -m inkwellobservability)
bench.py latency benchmark (make bench)
providers/
base.py EmbeddingProvider interface
stub.py deterministic offline embeddings (hot path)
real.py HTTP embedding client (optional, never used in tests)
tests/
docs/adr/ architecture decision recordsThe request path: server parses a JSON-RPC line and dispatches tools/call
to service, which validates arguments, enforces resource guards, and hands
LogRecords to the TriageEngine. The engine normalizes each message to a
template, embeds it via the configured EmbeddingProvider, and assigns it to
the nearest cluster in a single greedy pass.
Two mechanisms make the 150ms budget a property of the design rather than a hope:
Bounded cluster count
K. Assignment scans existing centroids, so per-record cost isO(K * dim). OnceKclusters exist, further distinct log shapes fold into the nearest cluster instead of spawning new scans. Total cost isO(N * K * dim)with a known constant.A wall-clock deadline. The clustering loop checks elapsed time on a coarse stride and, once the budget is spent, folds the remaining records into existing clusters and flags the result
degraded— a partial but usable ranking, returned inside the budget, rather than running long.
Embeddings on the hot path come from a deterministic feature-hashing provider
(StubEmbeddingProvider) with no network hop. RealEmbeddingProvider is an
optional quality upgrade behind the same interface; it is never required for
the tests, which run fully offline. The reasoning behind these calls is
recorded in docs/adr/.
Install
make venv # python3 -m venv .venv
make install # .venv/bin/python -m pip install -e .[dev]All Makefile targets use $(PY) (default .venv/bin/python); override it to
use a different interpreter, e.g. make test PY=python3.
Quickstart
Run the test suite and the latency benchmark:
make test PY=python3
make bench PY=python3make bench prints, for a 2000-record window:
records/window : 2000
budget : 150.0 ms
p50 : ...
p99 : ...
within budget : TrueUse the engine directly as a library:
from inkwellobservability import TriageEngine, LogRecord
engine = TriageEngine() # defaults: threshold 0.55, K=64, budget 150ms
records = [
LogRecord.of("payment gateway timeout for order 4821", level="ERROR"),
LogRecord.of("payment gateway timeout for order 90", level="ERROR"),
LogRecord.of("user 12 login failed", level="WARNING"),
]
result = engine.triage(records)
for cluster in result.clusters: # ranked, worst first
print(cluster.size, cluster.max_level.name, cluster.template)
print("degraded:", result.degraded, "elapsed_ms:", result.elapsed_ms)Ranking weights frequency by worst severity seen (size * 4**level): a small
burst of CRITICAL out-ranks a flood of INFO, while a large ERROR cluster still
out-ranks a single CRITICAL. See docs/adr/0004-severity-weighted-ranking.md.
As an MCP server
The entry point speaks MCP (JSON-RPC 2.0) over stdio, one JSON object per line:
python -m inkwellobservability # serve on stdin/stdout
python -m inkwellobservability --version
python -m inkwellobservability --helpAfter make install the same server is on PATH as inkwell-observability.
It exposes one tool, triage_logs, taking either inline records or a path
to a JSON-lines file, plus an optional budget_ms. Piping two requests in:
printf '%s\n' \
'{"jsonrpc":"2.0","id":1,"method":"initialize"}' \
'{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"triage_logs","arguments":{"records":[{"message":"payment timeout order 1","level":"ERROR"},{"message":"payment timeout order 2","level":"ERROR"}]}}}' \
| python -m inkwellobservabilityThe tool result carries both a text block (JSON) and structuredContent with
clusters (ranked worst-first), total_records, cluster_count,
elapsed_ms, budget_ms, and degraded.
Logs are JSON lines on stderr (stdout is reserved for protocol messages).
Every triage call logs its timing; a call that spends its budget logs at
WARNING with degraded: true.
Configuration reference
All configuration is environment-driven and validated at startup; an invalid value exits non-zero before the server starts serving.
Variable | Default | Meaning |
|
|
|
|
| embedding dimension |
|
| cosine threshold for joining a cluster |
|
| cluster cap |
|
| per-call latency budget |
|
| resource guard: max records per call |
|
| resource guard: max bytes per message |
|
|
|
When INKWELL_PROVIDER=real, the real provider also reads
INKWELL_EMBED_ENDPOINT and INKWELL_EMBED_API_KEY. If either is missing (or
the backend is unreachable at construction), the service logs a warning and
degrades to the stub provider rather than failing to start.
Failure handling
Malformed JSON-RPC line, bad envelope, or unknown method -> JSON-RPC error.
Bad tool arguments, missing/unreadable file, malformed JSONL line, or a resource guard trip -> a
tools/callresult withisError: trueand a message; the server loop stays up.Oversized batch or message is rejected up front so one request cannot blow the latency budget for others.
Known limitations
Per-call windows, no streaming state. Each
triage_logscall clusters the window it is given; clusters are not carried across calls. A rolling window with centroid eviction is deferred until the ingestion shape is fixed (TODO inclustering.py). Seedocs/adr/0001.Feature-hashing embeddings are shallow. The default provider matches on shared tokens, not learned meaning, so paraphrases with no common words ("disk full" vs "no space left on device") will not cluster together. Point
INKWELL_PROVIDER=realat an embedding backend for semantic recall, at the cost of the offline guarantee. Seedocs/adr/0002.Greedy clustering is order-sensitive. Assignment depends on arrival order; a different permutation can yield slightly different centroids. This is an accepted tradeoff for the single-pass cost bound (
docs/adr/0001).Single tool, no auth. The MCP surface is
triage_logsonly, and the stdio transport assumes a trusted local client. Network transport and authentication are out of scope for this deliverable.
Lockwood Group is an illustrative client; this repository is a self-directed reference implementation built to work end to end.
Available Tools
1 tooltriage_logsA
Cluster a window of log records by semantic similarity and rank the clusters for incident triage. Returns clusters worst-first.
| Name | Required | Description | Default |
|---|---|---|---|
| path | No | Path to a JSON-lines file of log records (alternative to 'records'). | |
| records | No | Inline log records. | |
| budget_ms | No | Override the p99 latency budget for this call. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations exist, so the description carries the behavioral burden. It usefully discloses that clustering is semantic and that clusters are ordered worst-first, but it does not mention latency budget behavior, error modes, or whether the operation is read-only.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is a single dense sentence followed by a short return-value note. Every phrase adds information, and there is no filler or redundant restating of the tool name.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
This is a complex clustering tool with no annotations and no output schema, so the description must do more. It conveys the high-level purpose but leaves important context unspecified, such as how a 'window' is determined, whether path and records are alternatives, and what the returned cluster objects look like.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so the schema already documents path, records, and budget_ms. The description adds no extra meaning about how these parameters relate to the 'window' of logs, so it stays at the baseline 3.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description names a specific verb and resource: 'Cluster a window of log records by semantic similarity and rank the clusters for incident triage.' It also adds a concrete output behavior, 'Returns clusters worst-first,' which clearly distinguishes it from generic log-processing tools.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The phrase 'for incident triage' provides clear intended context, and with no sibling tools, explicit alternative routing is not required. It could be more explicit about when not to use the tool, but the context is clear enough for an agent to select it.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections. Dates show when Glama detected each change.
1 tool update
v0.1.0- First observed
triage_logs
TDQS
With only a single tool, there is no possibility of confusion or overlap between tools. The tool's purpose is clear and unambiguous.
The single tool name 'triage_logs' follows a clear verb_noun pattern in snake_case. Without other tools to compare, there is no inconsistency.
The server is named for observability yet exposes only one tool. A single tool is too few for the apparent breadth of an observability platform, making the count inadequate.
The tool only performs log triage via clustering. Missing are core observability operations such as querying, filtering, retrieving, or alerting, leaving the surface severely incomplete for the domain.
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Connectors
Syslog receiver and MCP server for homelab log intelligence.
Syslog receiver and MCP server for homelab log intelligence.
MCP server for progressive tool usage at any scale (see https://klavis.ai)
Related MCP Servers
- AlicenseNot gradedqualityDmaintenanceAn MCP server for intelligent log analysis providing semantic search, error pattern clustering, and smart error detection. It enables users to process, vectorize, and query local logs to efficiently identify issues and generate AI-powered summaries.MIT
- AlicenseAqualityDmaintenanceMCP server for log file analysis. Gives LLMs the ability to efficiently analyze large log files without loading them into context.799MIT
- FlicenseNot gradedqualityCmaintenanceCompresses log files into templates and statistics using Drain3, and exposes them to AI assistants via an MCP server for efficient log monitoring and anomaly detection.-
- AlicenseBqualityDmaintenanceSmall production-oriented MCP server for diagnosing incidents from Elasticsearch logs with unknown schema. It provides tools for log discovery, retrieval, and issue diagnosis.6MIT
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/J-X0/lockwood-group-observability-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server