observer-launch-mcp
README.md
# observer-launch-mcp
Ask Claude questions about your [Observe](https://observeinc.com) data --
recent errors, common failures, log volume by pod -- without writing OPAL by
hand. This is an MCP server that gives Claude direct, live access to your
Observe tenant.
## Setup (2 steps)
**1. Run the setup wizard.** It asks for your Observe credentials and
dataset aliases, then registers itself with Claude Code automatically:
```bash
npx @aryanbansal-launch/observer-launch-mcp init
```
You'll be asked for:
- **Observe customer ID** -- a number, found in your Observe URL.
- **Observe API key** -- create one at **Manage account → My API tokens**.
(Not an ingest/datastream token -- those look similar but don't work here.)
- **Cluster** -- e.g. `eu-1`. Leave blank if you're on the default US tenant.
- **Dataset aliases** (optional) -- friendly names for datasets you'll ask
about often, e.g. `launch-management` → `41249178`. Find the ID in the
Observe UI: open a dataset, it's in the URL. You can skip this and use raw
dataset IDs instead, or add aliases later.
**2. Restart Claude Code** (or start a new session) so it picks up the new
server. Confirm it worked:
```
Is the Observe connection working?
```
That's it -- the other tools below are now available in chat.
<details>
<summary>Prefer to register it by hand instead of the wizard?</summary>
```bash
claude mcp add observe -s user \
-e OBSERVE_CUSTOMER_ID=<your id> \
-e OBSERVE_API_KEY=<your token> \
-e OBSERVE_CLUSTER=eu-1 \
-e OBSERVE_DATASETS='{"launch-management":"41249178"}' \
-- node <path to installed server.js>
```
Run `npx @aryanbansal-launch/observer-launch-mcp init` once first if you just
want that absolute path -- it prints the exact command it would run, right
before running it. **Use a direct `node <path>` command, not `npx <package>`
as the final entry** -- Claude Code spawns registered servers with a bare
environment (no PATH), and `npx`'s own shebang needs PATH to find `node`, so
`npx`-as-command silently shows "Failed to connect." The wizard already
avoids this for you.
</details>
## What you can ask
Once it's set up, just talk to Claude naturally:
- *"Is the Observe connection working?"*
- *"What service/dataset aliases do I have configured?"*
- *"Show me recent errors from launch-management in the last hour"*
- *"What's our most common error in launch-nginx this week?"*
- *"Which pods are logging the most in launch-management-bg right now?"*
- *"How much stderr output is launch-nginx producing in the last 6 hours?"*
- *"Count log lines per pod in launch-telemetry over the last hour"*
- *"Pull the last 20 raw log lines from launch-logs-bg-exporter"*
For anything not covered by a specific question shape, just describe what
you want -- Claude will write the OPAL pipeline for you using the general
query tool.
## Tools reference
| Tool | Use for |
|---|---|
| `observe_health_check` | "Is the connection working?" |
| `list_known_services` | "What datasets/aliases do I have?" |
| `get_service_errors` | Recent raw errors for one service, most recent first |
| `get_error_summary` | Top recurring errors ranked by count -- fast even over days/weeks |
| `get_log_stats` | Log counts grouped by pod/node/container/stream/namespace |
| `run_opal_query` | Anything else -- arbitrary OPAL pipeline against any dataset |
`get_error_summary` and `get_log_stats` aggregate server-side, so they stay
fast regardless of the time window. `get_service_errors` and `run_opal_query`
will add a warning if you query a 6h+ window without aggregating, since raw
row dumps over a wide window risk Observe's 100k row cap.
### Log formats
Datasets in one tenant rarely share a log schema, so `get_service_errors` and
`get_error_summary` detect the format per dataset (once per process) and build
the matching query:
| Format | Looks like | Errors are |
|---|---|---|
| `level` | `{"level":"error","message":...,"context":...}` — app services | `level` in error/fatal/critical/panic/… |
| `http` | `{"http.status":"500","http.method":...}` — nginx/access logs | `http.status` 5xx (pass `minStatus=400` for 4xx too) |
| `text` | anything unstructured | keyword match on the raw line |
Every result says which format was detected. To skip the probe, declare it in
your dataset config:
```json
{ "launch-nginx": { "id": "41250854", "shape": "http" } }
```
Plain `"alias": "id"` entries still work and are auto-detected.
**Coverage is reported, not hidden.** On `level` datasets, some lines have no
parseable `level` field (a stack trace, a bare string, non-JSON output) and a
severity filter cannot see them. When that share exceeds 1% the tools say so:
```
Note: 529 of 2136 rows (24.8%) have no parseable "level" field and were not
scanned. Pass includeUnparsed=true to keyword-match those too.
```
`includeUnparsed=true` keyword-scans those lines. It is opt-in because keyword
matching also flags `info` lines that merely contain the word "error".
## Configuration reference
| Variable | Required | Description |
|---|---|---|
| `OBSERVE_CUSTOMER_ID` | yes | Your Observe customer ID (numeric). |
| `OBSERVE_API_KEY` | yes | A bearer token from **Manage account → My API tokens** (or a service account token for long-lived use). |
| `OBSERVE_CLUSTER` | no | e.g. `eu-1`. Leave unset for the default (US) tenant. |
| `OBSERVE_BASE_URL` | no | Full override if your tenant doesn't follow the `{id}.{cluster}.observeinc.com` pattern. |
| `OBSERVE_LOOKUP_TIMEOUT_SEC` | no | Query timeout in seconds (default 300). |
| `OBSERVE_DATASETS` | no | Inline JSON mapping alias → dataset ID, e.g. `{"logs":"41001999"}`. An entry may be `{"id":"...","shape":"level\|http\|text"}` to skip format detection. |
| `OBSERVE_DATASETS_FILE` | no | Path to a JSON file with the same shape, for larger configs. |
| `OBSERVE_MAX_RESPONSE_BYTES` | no | Cap on a single tool response (default 200000). Past it, rows are dropped and the response says so. |
| `OBSERVE_KEEP_INTERNAL_COLS` | no | Set to `1` to keep the `link_*`, `containerId`, and `clusterUid` columns that are stripped by default. |
Dataset IDs are tenant-specific and intentionally not bundled with this
package. Without any configured, alias lookups fall through and you can
still pass raw numeric dataset IDs directly to any tool.
## Troubleshooting
- **Health check fails** → your API key is probably an ingest/datastream
token, not one from **Manage account → My API tokens**. Regenerate it.
- **`claude mcp list` shows "Failed to connect"** on a manually-registered
entry → check the command is `node <path>`, not `npx <package>` (see the
callout above).
- **A tool returns "Unknown service/dataset"** → run `list_known_services`
to see what's configured, or pass a raw numeric dataset ID instead.
- **`get_service_errors`/`get_error_summary` return nothing useful** → check
the "Detected log format" note on the result. If it guessed wrong, pin the
format explicitly with `{"alias": {"id": "...", "shape": "http"}}` (see
[Log formats](#log-formats)). If it says `text`, your logs have no severity
field and matching is keyword-based.
- **Errors you can see in the Observe UI are missing from the results** → look
for the coverage note. Lines with no parseable `level` are skipped by
default; pass `includeUnparsed=true`.
## Contributing
```bash
npm install
npm run build # tsc, then chmod +x on the bin entry
npm publish # only if you intend to ship a new version
```
Copy the `get_error_summary`/`get_log_stats` pattern in `src/run-server.ts`
for other recurring OPAL queries (e.g. cost-by-service, latency
percentiles) -- give the tool a descriptive name/description so Claude
picks it over `run_opal_query` when appropriate.
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues