# GlitchTip Read-Only Error/Event Tools — Design Notes - **Status:** Design (implementation-ready notes; **no implementation in this repo**) - **Issue:** #73 (umbrella: #75; boundary decision: ADR-0001, #71) - **Related:** #74 (GlitchTip→Gitea filing workflow — composes these read tools), #78 (dedup/linking, child of #74), #76 (per-service profile schema) - **Date:** 2026-07-02 ## 1. Purpose and scope Define the minimum **read-only** GlitchTip MCP tool set that lets an LLM answer: *"What unresolved errors does project X have (by environment/release), and what is this specific error?"* — with privacy-safe output suitable for LLM context, issue bodies, and audit logs. Strictly read-only, per ADR-0001: - **No mutation tools** — no resolving/ignoring/assigning issues, no comment posting, no project/team/key administration, no deletes. - **No automatic GlitchTip→Gitea filing** (that is #74's *orchestrated, explicitly-invoked* workflow; it composes these read tools and Gitea write tools — never one dual-credential server). - **This server never holds Gitea write credentials.** ## 2. Boundary placement (namespace pending) These tools belong to the GlitchTip observability boundary of the MCP Control Plane — `glitchtip-mcp` (ADR-0001's recommendation), `observability-mcp`, or folded into `ops-mcp`. **ADR-0001 open owner decision #2 picks the name; this design does not assume it.** Tool names below use the `glitchtip_` prefix for readability and rename mechanically with the decision. Fixed regardless of the name (per `tool-boundaries.md`, `credential-isolation.md`): - Own server process, own `.env`, GlitchTip credentials only. - No Gitea, Jenkins, or Ops tokens in this runtime; no GlitchTip token anywhere else. ## 3. API surface note (Sentry compatibility) GlitchTip implements a Sentry-compatible REST API (`/api/0/...` — organizations, projects, issues, events). The design targets **GlitchTip's documented subset** only; Sentry-only endpoints must not be assumed. The implementation should pin against a tested GlitchTip version and treat missing endpoints/fields as degraded-but-safe (omit field, never crash). ## 4. Minimum read-only tool set | Tool | Purpose | |---|---| | `glitchtip_whoami` | Verify authenticated identity + active profile (mirror of `gitea_whoami`; fail-closed identity proof) | | `glitchtip_list_projects` | Projects visible to the token (org-scoped), with pagination bounds | | `glitchtip_list_unresolved` | Unresolved issues for a project, filterable (§6), sorted by last-seen | | `glitchtip_get_issue` | Safe detail of one issue (fields §5) | | `glitchtip_recent_events` | Recent events for an issue (summaries only, §5) | | `glitchtip_search` | Issue search within a project (query + filters §6) | All tools are `GET`-only. No tool issues PUT/POST/DELETE. ## 5. Privacy: field-level allowlist (the core rule) Error events routinely contain PII and secrets (request bodies, cookies, headers, tokens, user emails/IPs, local variables). Therefore: **allowlist projection only — raw event/issue payloads are never passed through.** ### Issue-level safe fields (`glitchtip_list_unresolved`, `glitchtip_get_issue`, `glitchtip_search`) | Field | Notes | |---|---| | `issue_id` | GlitchTip issue ID (dedup key for #78) | | `fingerprint` | When available (dedup key for #78) | | `title` / `culprit` | Error type + short message/transaction — redactor-passed | | `project` | Slug | | `level` | error/warning/… | | `status` | unresolved/… | | `environment` | When filtered/available | | `release` | Version string | | `first_seen` / `last_seen` | ISO-8601 UTC | | `event_count` / `user_count` | Numbers only — never user identities | | `permalink` | GlitchTip web URL (the "link, not dump" principle) | ### Event-level safe fields (`glitchtip_recent_events`) `event_id`, `timestamp`, `level`, `environment`, `release`, redactor-passed `message`, and a **stack summary** only: top N (default 5) frames as `module/filename:function:line` — in-app frames preferred. ### Redact / omit — never returned Request headers; cookies; auth/session fields; user emails, usernames, IPs; request/form bodies; query strings; local variables; full raw stack frames (source context lines); SDK/device metadata beyond platform name; breadcrumbs; any `extra`/`context` blobs. Full raw frames or request context require a **separate, explicitly approved** operation (`glitchtip.event.read_raw`) that is absent from default profiles — same pattern as `jenkins.console.read` in the #72 design. Even then, output passes the shared secret redactor; redaction failure ⇒ error, never raw text. **Default output = fingerprint / release / summary + permalink.** The permalink carries the human to the full data in GlitchTip's own UI, where its access control applies — the MCP layer does not re-serve raw payloads. ## 6. Filtering and pagination Filters (all optional, combinable): `project` (required for issue/event queries), `environment`, `release`, `fingerprint`, free-text `query` (GlitchTip search syntax, e.g. `is:unresolved`). Pagination: cursor-based per the API. Bounds: per-page cap 50; default overall cap 100 items; hard cap `max_pages` (default 10) against runaway loops — mirroring `gitea_auth.api_get_all`. Truncation is **explicit** in the return (`"truncated": true`) — never silent. ## 7. Credentials and profile requirements Per-service profile model (`gitea-execution-profiles.md`, extended by #76): - Env/config: `GLITCHTIP_URL`, `GLITCHTIP_ORG`, `GLITCHTIP_TOKEN_SOURCE_NAME` (secret **name** only; value resolved at runtime, never logged/committed). - Profile: e.g. `glitchtip-readonly` with namespaced `allowed_operations: ["glitchtip.read", "glitchtip.event.read"]` (+ `glitchtip.event.read_raw` only with explicit approval); `forbidden_operations: ["glitchtip.issue.mutate", "glitchtip.admin"]` belt-and-braces though no mutating tool exists. - Missing URL/org/token/profile ⇒ **fail closed** before any network call. - Read-only ⇒ no confirmation gates; identity (`glitchtip_whoami`) must work so workflows can prove which account they read as. ## 8. Failure behavior (fail closed, clear, safe) | Condition | Behavior | |---|---| | Unknown project/issue | Explicit `{"found": false, ...}` — no fuzzy matching | | GlitchTip unreachable (DNS/timeout) | `"network error contacting GlitchTip: "` — mirror `gitea_auth.api_request` conversion | | 502/503/504 | "GlitchTip upstream unavailable" | | 401/403 | "GlitchTip auth failed / insufficient permissions" — no credential echo | | 429 | Honor Retry-After with capped jittered backoff (as `gitea_auth`) | | Malformed JSON | "malformed JSON response from GlitchTip" — no raw-body dump | | Missing profile/creds | Fail closed before any network call (§7) | All error text passes the shared secret redactor. ## 9. Testing strategy (mocked; for the implementing package) Mocked-GlitchTip unit tests only, per `docs/developer-testing-guidelines.md`: - Assert method is always `GET`; URL/filter/cursor shape correct. - **Projection tests:** response fixtures containing emails, IPs, cookies, headers, request bodies, locals, full frames ⇒ none appear in output (explicit negative assertions per §5's redact list). - Stack summary: top-N frame cap enforced; source-context lines absent. - Pagination: per-page/overall/max-pages caps; explicit `truncated` flag. - Filters: environment/release/fingerprint/query passed through correctly. - Failure matrix of §8 incl. no-token-in-error assertions. - Profile gate: missing/insufficient profile ⇒ no network call (`mock_api.assert_not_called()` pattern). - `read_raw` op absent ⇒ raw-frame request refused without an API call. ## 10. Implementation-readiness checklist Ready to implement once: 1. ADR-0001 owner decision #2 (namespace/placement) is made — mechanical rename of the `glitchtip_` prefix if needed. 2. ADR-0001 owner decision #1 (repo home) is made. 3. #76 profile schema exists (or a minimal `glitchtip-readonly` profile is hand-rolled to the same rules). 4. A pinned GlitchTip version is chosen for API-subset testing (§3). Explicitly **not** unlocked by this document: any GlitchTip mutation, any automatic Gitea filing (#74 designs that as a gated, explicitly-invoked orchestrated workflow), any Gitea credentials in this boundary.