Oddyssey MCP server: pilots the local observability stack for CLI coding agents
oddyssey-mcp defines 6 tools with detailed descriptions (avg 380 chars, well above 194-char baseline). All tools have clear verb-noun naming (odd_stack_up, odd_stack_down, etc.). However, input schemas are largely absent or minimal: only odd_config_set shows a schema fragment; others lack visible type definitions, parameter descriptions, and output schemas. Descriptions are comprehensive but verbose, some exceed 1024 chars. No error handling guidance, no parameter constraints (enums, ranges), and no output schema documentation. Tool composition is sound (each does one thing), but schema rigor is weak.
Read the global oddyssey configuration: the configured stack (local, or a remote backend), the configured deployment environment (null when none is set), and the local stack host ports (defaults applied; invalid stored values are listed in invalid_ignored). Also returns stack_config: non-secret targeting values (identifiers, names, regions) persisted per backend, keyed <stack> or <environment>-<stack>; effective: the entry resolved for the configured pair - {"stack", "environment", "stack_config_key", "stack_config"} where stack_config_key is "<environment>-<stack>" when an entry is persisted for the configured environment, the plain "<stack>" otherwise (the fallback; no merge of the two), and stack_config is that entry ({} when nothing is persisted) - so a consumer of the configured pair never composes the fallback itself; on the local stack the environment is inert (effective.environment is null, the key "local"); and version (read-only, never accepted by odd_config_set): the installed oddyssey-mcp package version (null when the server runs from a source checkout that is not installed as a distribution). Takes no argument.
Update the global oddyssey configuration (partial merge). config example: {"stack": "datadog"} or {"local": {"grafana_port": 3300}} or {"stack_config": {"azure-monitor": {"workspace": "<guid>"}}} or {"environment": "prod", "stack_config": {"prod-cloudwatch": {"log_group": "<log_group>"}}} stack is one of: local (the local stack - the default), grafana (a REMOTE Grafana - the CLI context says which instance), azure-monitor, cloudwatch, datadog, dynatrace - or a custom stack (a backend the package does not ship, described by a stack directory in the observed repository) declared under custom in the same call or an earlier one: {"stack": "seq", "custom": {"seq": {"stack_config_fields": ["base_url"]}}} A custom name is kebab-case, never a built-in one and never one that reads as <environment>-<known stack> (prod-cloudwatch, or prod-seq once seq is declared - refused in both directions); its declaration lists the stack_config fields the stack's guide names (an empty list when it persists nothing), and a re-declaration replaces the list. The server never reads the stack's guide - the caller derives the declaration from it. Custom declarations survive a switch to a built-in stack; {"custom": {"seq": null}} removes one (refused while seq is the configured stack, unless the same call switches away). Removing a declaration keeps its stack_config values - a later re-declaration finds them again - and until then odd_config_get lists them as invalid_ignored; {"stack_config": {"seq": null}} clears them. Switching to the local stack is {"stack": "local"}. Changing a port while a stack container exists RESETS the stack immediately so the configuration is always applied: this WIPES all stored telemetry machine-wide (the result embeds the reset outcome, including services_wiped). The auto-reset carries the old container's user-set environment forward to the recreated one, best-effort: the result's env_preserved field lists the carried variable names (never values). If reading the old container failed, env_preserved is empty; the recreation still reapplies whatever stack_config.local persists, so only never-persisted variables (credential-named ones) are lost - re-run odd_stack_reset with the desired env to reapply those. The auto-reset applies the carried env without re-persisting it, so a variable deleted with null in this same call stays deleted across the port change. The MCP server's own telemetry export honors a changed OTLP port only after the MCP server restarts, and applications configured against the old ports keep exporting to them - their OTEL_EXPORTER_OTLP_ENDPOINT must be updated to the new otlp_endpoint. stack_config is keyed <stack> or <environment>-<stack> - one entry per deployment environment next to the stack's plain one, e.g. {"stack_config": {"prod-cloudwatch": {"log_group": "<log_group>"}}}: the environment is kebab-case (^[a-z]([a-z0-9-]*[a-z0-9])?$, never "unknown"), the key is parsed by suffix (dev-azure-monitor is dev + azure-monitor, pre-prod-cloudwatch is pre-prod + cloudwatch - the longest known stack wins), a key whose suffix is no known stack is rejected, and local takes no prefix. stack_config is merged per key (other entries' payloads are untouched) and never boots or resets the stack container; values must be non-secret scalars - credentials stay in the CLI's own auth store, referenced by name only. Each stack accepts only its own documented field set, for its prefixed entries too (e.g. azure-monitor: subscription, resource_group, workspace, app_insights_app; grafana: context - the name of the gcx context the runs use, the user's active one when absent; datadog/dynatrace: none, their CLI context carries targeting; a custom stack: its declared list) - an undocumented key is rejected, writing nothing, EXCEPT as a null deletion, which is always accepted so a stray key can still be cleaned up.
Input schemas missing or incomplete for 5 of 6 tools. Only odd_config_set shows a partial schema; odd_stack_down, odd_stack_status lack any visible type definitions.
No output schemas documented. Descriptions mention returned fields (e.g., 'env_applied', 'daemon', 'services_wiped') but no formal schema. LLMs cannot plan downstream calls without knowing response structure.
Descriptions exceed 1024 chars for odd_stack_up, odd_stack_reset, odd_config_set, and odd_config_get. Verbose explanations of internal behavior (env persistence, credential redaction, stack_config merging) dilute signal and waste tokens. Trim to 200 - 400 chars and move implementation details to docs.
Inferred effective spec: <=2025-11-25.
| Scored | Grade | Overall | Spec posture | Rubric |
|---|---|---|---|---|
| 2026-09-23 | D | 55 | <=2025-11-25 | v2 |
Stop and remove the local LGTM stack; stored telemetry does not survive. The stack is shared by every project on this machine, so their data is destroyed too.
Wipe ALL stored telemetry (traces, metrics, logs, profiles) and return a fresh, ready stack. env adds environment variables to the recreated container (see odd_stack_up); unlike on up, a reset always recreates, so env always applies. The recreation reapplies the env persisted in stack_config.local and persists the explicit non-credential entries it applied (odd_stack_up's creation contract), so the reset needs no env at all to keep a previously applied configuration. The wipe is machine-wide and irreversible: one shared stack per machine, so data from every project ever observed on it is destroyed, not just the current one. The result's services_wiped field lists the service.name values that were stored. If it may contain services outside the current project, warn the user before calling this tool. services_wiped always includes oddyssey-mcp (this server observes itself and exports to the stack it pilots) and otelcol-contrib (the embedded collector's own metrics): those two are never another project's leftover state - only other names are.
Check whether the local LGTM stack is up (Prometheus, Tempo, Loki, and Pyroscope ready) - plus the container's identity: image tag, created/started timestamps, and user-set env (credential-named values redacted to null, all four null when there is no container). daemon is "ok" when the Docker daemon answers, or "unreachable" with daemon_remedy (the one-line remedy) when it does not.
Start the local LGTM observability stack (Grafana, Tempo, Prometheus, Loki, Pyroscope, OTLP). env adds environment variables to the container - the otel-lgtm image is configured exclusively through them (PROMETHEUS_EXTRA_ARGS, LOKI_EXTRA_ARGS, TEMPO_EXTRA_ARGS, GF_*, ...); explicit entries override the embedded defaults. Docker only applies env at container creation: when a container already exists the result carries env_applied: false and the requested env is NOT active - run odd_stack_reset with the same env to apply it (destroys stored telemetry). A creation persists the applied env into stack_config.local and reapplies what is persisted there (explicit entries win), so a creation-time choice survives later recreations without repeating it. Credential-named variables (headers, tokens, secrets, passwords) are applied but never persisted - the result's env_reapplied / env_persisted / env_not_persisted fields say exactly what happened; remove a persisted variable with odd_config_set's null deletion.
No error handling guidance. Descriptions do not explain what to do if Docker daemon is unreachable, container creation fails, or config validation rejects a value. LLMs have no recovery path.
Parameter 'env' in odd_stack_up and odd_stack_reset lacks description. What format? What keys are valid? Are there constraints on credential-named variables? LLMs cannot infer from name alone.