oops-errors-mcp 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,3 +1,176 @@
1
- # Temporary Holding Version
1
+ # oops-errors-mcp
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ [Model Context Protocol](https://modelcontextprotocol.io) server for
4
+ [oops](https://oops.3m4.net), the open-source error-tracking service. Lets an
5
+ AI agent (Claude Code, Claude Desktop, Cursor, the MCP Inspector, your own
6
+ app) list and triage issues, read stack traces and breadcrumbs, resolve or
7
+ ignore errors, check stats and manage projects — and report its *own*
8
+ failures into a project — with **role-based tool access**, a **human approval
9
+ step** for destructive actions, and an **audit trail**.
10
+
11
+ ```
12
+ npx oops-errors-mcp # stdio (local hosts)
13
+ https://oops.3m4.net/mcp # Streamable HTTP (remote hosts)
14
+ ```
15
+
16
+ ## Quick start
17
+
18
+ 1. Create an API key in the dashboard (`/dashboard/settings`) and pick a role.
19
+ 2. Register the server with your host.
20
+
21
+ **Claude Code**
22
+
23
+ ```bash
24
+ claude mcp add oops --scope user \
25
+ --env OOPS_API_KEY=oops_live_… \
26
+ -- npx -y oops-errors-mcp
27
+ ```
28
+
29
+ **Claude Desktop / Cursor / any JSON config**
30
+
31
+ ```json
32
+ {
33
+ "mcpServers": {
34
+ "oops": {
35
+ "command": "npx",
36
+ "args": ["-y", "oops-errors-mcp"],
37
+ "env": { "OOPS_API_KEY": "oops_live_…" }
38
+ }
39
+ }
40
+ }
41
+ ```
42
+
43
+ **Remote (Streamable HTTP)** — no install, the key travels as a Bearer token:
44
+
45
+ ```bash
46
+ claude mcp add --transport http oops https://oops.3m4.net/mcp \
47
+ --header "Authorization: Bearer oops_live_…"
48
+ ```
49
+
50
+ Then ask: *"what is breaking in checkout today?"*, *"show me issue #12 and
51
+ its stack"*, *"which errors affect the most users since release 1.4.2?"*,
52
+ *"resolve every issue from the old release"*, *"triage the API project"*,
53
+ *"report this failure to the agents project"*.
54
+
55
+ ## Configuration
56
+
57
+ | Variable | Default | Meaning |
58
+ |------------------|-------------------------------|---------|
59
+ | `OOPS_API_KEY` | — | Required for stdio. The oops management API key (`oops_live_…`). |
60
+ | `OOPS_API_URL` | `https://oops.3m4.net/api` | API base URL. Self-hosters point this at their instance. |
61
+ | `OOPS_AUDIT_LOG` | `stderr` | `stderr`, `none`, or a file path (JSON lines). |
62
+ | `OOPS_MCP_PORT` | `3333` | HTTP entrypoint only. |
63
+
64
+ Project public keys (`oops_pub_…`) are **not** accepted: they can only call
65
+ the ingest API and carry no role.
66
+
67
+ ## Roles and tools (RBAC)
68
+
69
+ Each API key has a role. The server asks the API for the key's scopes once
70
+ (`GET /apikeys/me`) and **only registers the tools those scopes allow**, so a
71
+ read-only key never even lists `purge_events`. Every call is re-checked at
72
+ call time, and the API enforces the same scopes independently — three layers,
73
+ and a bug in the server can hide access but never grant it.
74
+
75
+ | Tool | Scope | viewer | editor | admin | Notes |
76
+ |----------------------|-------------------|:------:|:------:|:-----:|-------|
77
+ | `list_projects` | `projects:read` | ✓ | ✓ | ✓ | open issues, events 24h / 30d, users 30d, last event; `q` |
78
+ | `get_project` | `projects:read` | ✓ | ✓ | ✓ | public key, time zone, allowed origins, switches |
79
+ | `list_issues` | `issues:read` | ✓ | ✓ | ✓ | `status`, `level`, `environment`, `release`, `q`, `period`, `sort`, paging |
80
+ | `get_issue` | `issues:read` | ✓ | ✓ | ✓ | uuid, `12` or `#12`; includes the latest event with its stack |
81
+ | `list_issue_events` | `issues:read` | ✓ | ✓ | ✓ | occurrences of one issue, newest first |
82
+ | `get_event` | `issues:read` | ✓ | ✓ | ✓ | frames, raw stack, tags, extra, breadcrumbs, user |
83
+ | `search_events` | `issues:read` | ✓ | ✓ | ✓ | raw events; `level`, `environment`, `release`, `q`, `issue`, `period` |
84
+ | `get_stats` | `issues:read` | ✓ | ✓ | ✓ | totals vs previous period, series, breakdowns; `period` or `from`/`to` |
85
+ | `get_live` | `issues:read` | ✓ | ✓ | ✓ | events in the last few minutes |
86
+ | `get_usage` | `usage:read` | ✓ | ✓ | ✓ | events / dropped / counts vs. plan limits |
87
+ | `report_error` | `projects:read` | ✓ | ✓ | ✓ | posts an event to `/ingest` with the project's public key |
88
+ | `create_project` | `projects:write` | | ✓ | ✓ | returns the public key + SDK tag |
89
+ | `update_project` | `projects:write` | | ✓ | ✓ | pause ingest, store raw IPs, time zone, allowed origins… |
90
+ | `rotate_public_key` | `projects:write` | | ✓ | ✓ | old key stops working immediately |
91
+ | `update_issue` | `issues:write` | | ✓ | ✓ | `status`: `resolved`, `ignored`, `open` (reopen) |
92
+ | `bulk_update_issues` | `issues:write` | | ✓ | ✓ | same status on ≤ 100 issues |
93
+ | `delete_project` | `projects:delete` | | | ✓ | **confirmation required** |
94
+ | `purge_events` | `events:delete` | | | ✓ | **confirmation required**; deletes every event and issue |
95
+ | `get_audit_log` | `audit:read` | | | ✓ | `action`, `actor_type`, `outcome`, `since`, paging |
96
+
97
+ Roles are defined once in
98
+ [`packages/shared/api_key_roles.json`](../shared/api_key_roles.json) and
99
+ shared by the API, the dashboard and this server.
100
+
101
+ Also exposed: resources `oops://projects` and `oops://projects/{id}/summary`,
102
+ and the **`triage`** prompt (`project`, `period?`): summarise the open issues
103
+ of a project — new vs regressed, most users affected, by environment and
104
+ release — and propose what to fix, resolve or ignore first. It only proposes;
105
+ status changes still go through `update_issue` after the user agrees.
106
+
107
+ ### Reporting from an agent
108
+
109
+ `report_error` lets the agent file its own failures: it reads the project to
110
+ get its public key and posts one event to `POST /api/ingest` with
111
+ `X-Project-Key`, `platform: "agent"` (override with `python`, `node`, …) and
112
+ `sdk: "oops-errors-mcp/<version>"`. Give `type` and/or `message`, optionally
113
+ `stack` (parsed server-side), `frames`, `url`, `release`, `environment`,
114
+ `user`, `tags`, `extra`, `breadcrumbs` and a `fingerprint` to control
115
+ grouping. The result is the event id and the issue `#number`, or `ok: false`
116
+ with a drop reason (`paused`, `origin`, `limit`) — never an error on the
117
+ agent's side.
118
+
119
+ ## Human approval for destructive actions
120
+
121
+ `delete_project` and `purge_events` are irreversible, so they never run on the
122
+ model's say-so alone:
123
+
124
+ - On hosts that support MCP **elicitation** (Claude Code, Inspector) the user
125
+ gets a native confirmation dialog showing exactly what will be deleted.
126
+ - On hosts that don't (and always over HTTP, which is stateless), the tool
127
+ refuses with `confirmation_required` until the model calls it again with
128
+ `confirm: true` — which it can only honestly do after asking the user.
129
+
130
+ `rotate_public_key` is not gated but is marked destructive: every SDK tag and
131
+ server-side reporter using the previous key stops working the moment it runs.
132
+ Prefer `update_project` with `ingest_enabled: false` over deleting a project.
133
+
134
+ ## Audit trail
135
+
136
+ Two complementary logs:
137
+
138
+ - **Server-side** (`audit_events` table, `GET /account/audit`, tool
139
+ `get_audit_log`): every mutation and every authorization denial, written in
140
+ the same transaction as the change.
141
+ - **Agent-side** (`OOPS_AUDIT_LOG`, JSON lines): every tool call the agent
142
+ attempted, including ones refused locally that never reached the API, with
143
+ duration, outcome (`ok|denied|error|cancelled|pending`) and compacted args.
144
+
145
+ ## Design notes
146
+
147
+ - **Structured outputs.** Every tool declares an `outputSchema` and returns
148
+ `structuredContent`; hosts can validate and chain results without parsing
149
+ prose. The text part is a compact summary (one line per issue or event, the
150
+ stack of the latest event top-first). Objects mirroring API responses allow
151
+ extra fields, so an additive API change never breaks client-side validation.
152
+ - **Errors are results.** API errors come back as `isError` with the stable
153
+ code (`limit_reached` + `which`, `key_taken`, `invalid_timezone`,
154
+ `invalid_origins`, `insufficient_scope`, `not_found`, `invalid_issue_ref`…)
155
+ so the model can recover instead of crashing.
156
+ - **stdio hygiene.** stdout is the protocol channel; all logging goes to stderr.
157
+ - **Stateless HTTP.** Each request builds a server bound to the caller's key;
158
+ tenants can never share state. The `/apikeys/me` lookup is cached 60 s per key.
159
+
160
+ ## Development
161
+
162
+ ```bash
163
+ bun install
164
+ bun test # in-memory MCP client ↔ server against a fake API
165
+ bun run build
166
+ OOPS_API_KEY=… OOPS_API_URL=http://localhost:8084/api bun run inspect # MCP Inspector
167
+ OOPS_API_KEY=… OOPS_API_URL=http://localhost:8084/api bun run dev:http # http://localhost:3333/mcp
168
+ ```
169
+
170
+ The Docker image (`packages/mcp-server/Dockerfile`) runs the HTTP flavour and
171
+ is wired into `deploy/docker-compose.yml` as the `mcp` service, exposed by
172
+ Caddy at `/mcp`.
173
+
174
+ ## License
175
+
176
+ MIT © Enrique Magallon Alvarez
@@ -0,0 +1,42 @@
1
+ export type AuditOutcome = "ok" | "denied" | "error" | "cancelled" | "pending";
2
+ export interface AuditEntry {
3
+ ts: string;
4
+ event: "server.start" | "tool.call";
5
+ transport: string;
6
+ actor: {
7
+ account: string;
8
+ prefix: string;
9
+ role: string;
10
+ };
11
+ tool?: string;
12
+ outcome?: AuditOutcome;
13
+ duration_ms?: number;
14
+ args?: Record<string, unknown>;
15
+ error?: {
16
+ code: string;
17
+ message: string;
18
+ };
19
+ meta?: Record<string, unknown>;
20
+ }
21
+ export interface AuditSink {
22
+ write(entry: AuditEntry): void;
23
+ }
24
+ export declare class StderrAuditSink implements AuditSink {
25
+ write(entry: AuditEntry): void;
26
+ }
27
+ export declare class FileAuditSink implements AuditSink {
28
+ private readonly path;
29
+ constructor(path: string);
30
+ write(entry: AuditEntry): void;
31
+ }
32
+ export declare class MemoryAuditSink implements AuditSink {
33
+ readonly entries: AuditEntry[];
34
+ write(entry: AuditEntry): void;
35
+ }
36
+ export declare class NullAuditSink implements AuditSink {
37
+ write(): void;
38
+ }
39
+ /** `OOPS_AUDIT_LOG`: "stderr" (default) | "none" | a file path. */
40
+ export declare function createAuditSink(setting: string | undefined): AuditSink;
41
+ /** Keep logged arguments small and free of giant payloads. */
42
+ export declare function compactArgs(args: unknown): Record<string, unknown> | undefined;
package/dist/audit.js ADDED
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Client-side audit trail for the MCP server: one JSON line per tool call.
3
+ *
4
+ * This complements (does not replace) the server-side `audit_events` table:
5
+ * the API records *what changed*; this log records *what the agent tried*,
6
+ * including calls that were refused locally and never reached the API.
7
+ *
8
+ * In stdio mode stdout is the protocol channel, so the default sink is stderr.
9
+ */
10
+ import { appendFileSync } from "node:fs";
11
+ export class StderrAuditSink {
12
+ write(entry) {
13
+ process.stderr.write(JSON.stringify(entry) + "\n");
14
+ }
15
+ }
16
+ export class FileAuditSink {
17
+ path;
18
+ constructor(path) {
19
+ this.path = path;
20
+ }
21
+ write(entry) {
22
+ appendFileSync(this.path, JSON.stringify(entry) + "\n");
23
+ }
24
+ }
25
+ export class MemoryAuditSink {
26
+ entries = [];
27
+ write(entry) {
28
+ this.entries.push(entry);
29
+ }
30
+ }
31
+ export class NullAuditSink {
32
+ write() { }
33
+ }
34
+ /** `OOPS_AUDIT_LOG`: "stderr" (default) | "none" | a file path. */
35
+ export function createAuditSink(setting) {
36
+ const value = (setting ?? "stderr").trim();
37
+ if (value === "" || value === "stderr")
38
+ return new StderrAuditSink();
39
+ if (value === "none")
40
+ return new NullAuditSink();
41
+ return new FileAuditSink(value);
42
+ }
43
+ /** Keep logged arguments small and free of giant payloads. */
44
+ export function compactArgs(args) {
45
+ if (!args || typeof args !== "object")
46
+ return undefined;
47
+ const out = {};
48
+ for (const [k, v] of Object.entries(args)) {
49
+ out[k] = typeof v === "string" && v.length > 200 ? v.slice(0, 200) + "…" : v;
50
+ }
51
+ return out;
52
+ }
53
+ //# sourceMappingURL=audit.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audit.js","sourceRoot":"","sources":["../src/audit.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAqBzC,MAAM,OAAO,eAAe;IAC1B,KAAK,CAAC,KAAiB;QACrB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,CAAC;IACrD,CAAC;CACF;AAED,MAAM,OAAO,aAAa;IACK;IAA7B,YAA6B,IAAY;QAAZ,SAAI,GAAJ,IAAI,CAAQ;IAAG,CAAC;IAC7C,KAAK,CAAC,KAAiB;QACrB,cAAc,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,CAAC;IAC1D,CAAC;CACF;AAED,MAAM,OAAO,eAAe;IACjB,OAAO,GAAiB,EAAE,CAAC;IACpC,KAAK,CAAC,KAAiB;QACrB,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC3B,CAAC;CACF;AAED,MAAM,OAAO,aAAa;IACxB,KAAK,KAAU,CAAC;CACjB;AAED,mEAAmE;AACnE,MAAM,UAAU,eAAe,CAAC,OAA2B;IACzD,MAAM,KAAK,GAAG,CAAC,OAAO,IAAI,QAAQ,CAAC,CAAC,IAAI,EAAE,CAAC;IAC3C,IAAI,KAAK,KAAK,EAAE,IAAI,KAAK,KAAK,QAAQ;QAAE,OAAO,IAAI,eAAe,EAAE,CAAC;IACrE,IAAI,KAAK,KAAK,MAAM;QAAE,OAAO,IAAI,aAAa,EAAE,CAAC;IACjD,OAAO,IAAI,aAAa,CAAC,KAAK,CAAC,CAAC;AAClC,CAAC;AAED,8DAA8D;AAC9D,MAAM,UAAU,WAAW,CAAC,IAAa;IACvC,IAAI,CAAC,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ;QAAE,OAAO,SAAS,CAAC;IACxD,MAAM,GAAG,GAA4B,EAAE,CAAC;IACxC,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAA+B,CAAC,EAAE,CAAC;QACrE,GAAG,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,CAAC,MAAM,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IAC/E,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC"}
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Minimal typed HTTP client for the oops management API (and the ingest
3
+ * endpoint, used by `report_error`).
4
+ *
5
+ * Deliberately dependency-free (global `fetch`) so the MCP server stays small
6
+ * and the client can be swapped for a fake in tests.
7
+ */
8
+ import type { AuditEvent, AuditParams, CreateProjectInput, Event, EventsParams, IngestEventInput, IngestResult, Issue, IssueStatus, IssuesParams, KeyInfo, Live, Page, Project, PurgeResult, Stats, StatsParams, Summary, UpdateProjectInput, Usage } from "./types.js";
9
+ /** An error the API returned on purpose (`{ error: { code, message, ...extra } }`). */
10
+ export declare class OopsApiError extends Error {
11
+ readonly status: number;
12
+ readonly code: string;
13
+ /** Extra error fields, e.g. `which` on `limit_reached`, `required_scope` on `insufficient_scope`. */
14
+ readonly extra: Record<string, unknown>;
15
+ constructor(status: number, code: string, message: string,
16
+ /** Extra error fields, e.g. `which` on `limit_reached`, `required_scope` on `insufficient_scope`. */
17
+ extra?: Record<string, unknown>);
18
+ }
19
+ export interface OopsClientOptions {
20
+ /** Base URL including the `/api` prefix, e.g. `https://oops.3m4.net/api`. */
21
+ baseUrl: string;
22
+ apiKey: string;
23
+ fetch?: typeof fetch;
24
+ }
25
+ export declare class OopsClient {
26
+ private readonly baseUrl;
27
+ private readonly apiKey;
28
+ private readonly fetchImpl;
29
+ constructor(opts: OopsClientOptions);
30
+ private request;
31
+ /** Identity + scopes of the calling key. */
32
+ me(): Promise<KeyInfo>;
33
+ listProjects(): Promise<{
34
+ items: Project[];
35
+ }>;
36
+ createProject(input: CreateProjectInput): Promise<Project>;
37
+ getProject(id: string): Promise<Project>;
38
+ updateProject(id: string, patch: UpdateProjectInput): Promise<Project>;
39
+ deleteProject(id: string): Promise<void>;
40
+ /** New public key; the old one stops working at once. Returns the updated project. */
41
+ rotatePublicKey(id: string): Promise<Project>;
42
+ summary(projectId: string): Promise<Summary>;
43
+ /** Purge every event and issue of the project. */
44
+ purgeEvents(projectId: string): Promise<PurgeResult>;
45
+ listIssues(projectId: string, params?: IssuesParams): Promise<Page<Issue>>;
46
+ /** `issueRef` is the issue uuid or its per-project number as a string (see `parseIssueRef`). */
47
+ getIssue(projectId: string, issueRef: string): Promise<Issue>;
48
+ updateIssue(projectId: string, issueRef: string, status: IssueStatus): Promise<Issue>;
49
+ bulkUpdateIssues(projectId: string, ids: string[], status: IssueStatus): Promise<{
50
+ updated: number;
51
+ }>;
52
+ listIssueEvents(projectId: string, issueRef: string, params?: {
53
+ page?: number;
54
+ per_page?: number;
55
+ }): Promise<Page<Event>>;
56
+ listEvents(projectId: string, params?: EventsParams): Promise<Page<Event>>;
57
+ getEvent(projectId: string, eventId: string): Promise<Event>;
58
+ stats(projectId: string, params?: StatsParams): Promise<Stats>;
59
+ live(projectId: string): Promise<Live>;
60
+ usage(): Promise<Usage>;
61
+ auditLog(params?: AuditParams): Promise<Page<AuditEvent>>;
62
+ /**
63
+ * Report one event into a project. Authenticated with the project's PUBLIC
64
+ * key (`X-Project-Key`), never the API key: that is the only credential the
65
+ * ingest endpoint understands. Always 202; `ok: false` carries a drop reason.
66
+ */
67
+ ingest(publicKey: string, event: IngestEventInput): Promise<IngestResult>;
68
+ }
package/dist/client.js ADDED
@@ -0,0 +1,146 @@
1
+ import { VERSION } from "./version.js";
2
+ /** An error the API returned on purpose (`{ error: { code, message, ...extra } }`). */
3
+ export class OopsApiError extends Error {
4
+ status;
5
+ code;
6
+ extra;
7
+ constructor(status, code, message,
8
+ /** Extra error fields, e.g. `which` on `limit_reached`, `required_scope` on `insufficient_scope`. */
9
+ extra = {}) {
10
+ super(message);
11
+ this.status = status;
12
+ this.code = code;
13
+ this.extra = extra;
14
+ this.name = "OopsApiError";
15
+ }
16
+ }
17
+ const enc = encodeURIComponent;
18
+ export class OopsClient {
19
+ baseUrl;
20
+ apiKey;
21
+ fetchImpl;
22
+ constructor(opts) {
23
+ this.baseUrl = opts.baseUrl.replace(/\/+$/, "");
24
+ this.apiKey = opts.apiKey;
25
+ this.fetchImpl = opts.fetch ?? globalThis.fetch;
26
+ }
27
+ async request(method, path, opts = {}) {
28
+ const url = new URL(this.baseUrl + path);
29
+ for (const [k, v] of Object.entries(opts.query ?? {})) {
30
+ if (v !== undefined && v !== "")
31
+ url.searchParams.set(k, String(v));
32
+ }
33
+ const headers = {
34
+ Accept: "application/json",
35
+ "User-Agent": `oops-errors-mcp/${VERSION}`,
36
+ ...(opts.headers ?? {}),
37
+ };
38
+ if (opts.auth !== false)
39
+ headers["X-API-Key"] = this.apiKey;
40
+ if (opts.body !== undefined)
41
+ headers["Content-Type"] = "application/json";
42
+ const res = await this.fetchImpl(url, {
43
+ method,
44
+ headers,
45
+ body: opts.body === undefined ? undefined : JSON.stringify(opts.body),
46
+ });
47
+ if (res.status === 204)
48
+ return undefined;
49
+ const text = await res.text();
50
+ let data = null;
51
+ try {
52
+ data = text ? JSON.parse(text) : null;
53
+ }
54
+ catch {
55
+ data = null;
56
+ }
57
+ if (!res.ok) {
58
+ const err = (data?.error ?? {});
59
+ const { code, message, ...extra } = err;
60
+ throw new OopsApiError(res.status, typeof code === "string" ? code : `http_${res.status}`, typeof message === "string" ? message : `HTTP ${res.status}`, extra);
61
+ }
62
+ return data;
63
+ }
64
+ /** Identity + scopes of the calling key. */
65
+ me() {
66
+ return this.request("GET", "/apikeys/me");
67
+ }
68
+ // --- Projects ---------------------------------------------------------- //
69
+ listProjects() {
70
+ return this.request("GET", "/projects");
71
+ }
72
+ createProject(input) {
73
+ return this.request("POST", "/projects", { body: input });
74
+ }
75
+ getProject(id) {
76
+ return this.request("GET", `/projects/${enc(id)}`);
77
+ }
78
+ updateProject(id, patch) {
79
+ return this.request("PATCH", `/projects/${enc(id)}`, { body: patch });
80
+ }
81
+ deleteProject(id) {
82
+ return this.request("DELETE", `/projects/${enc(id)}`);
83
+ }
84
+ /** New public key; the old one stops working at once. Returns the updated project. */
85
+ rotatePublicKey(id) {
86
+ return this.request("POST", `/projects/${enc(id)}/rotate-key`);
87
+ }
88
+ summary(projectId) {
89
+ return this.request("GET", `/projects/${enc(projectId)}/summary`);
90
+ }
91
+ /** Purge every event and issue of the project. */
92
+ purgeEvents(projectId) {
93
+ return this.request("DELETE", `/projects/${enc(projectId)}/events`);
94
+ }
95
+ // --- Issues and events --------------------------------------------------- //
96
+ listIssues(projectId, params = {}) {
97
+ return this.request("GET", `/projects/${enc(projectId)}/issues`, { query: params });
98
+ }
99
+ /** `issueRef` is the issue uuid or its per-project number as a string (see `parseIssueRef`). */
100
+ getIssue(projectId, issueRef) {
101
+ return this.request("GET", `/projects/${enc(projectId)}/issues/${enc(issueRef)}`);
102
+ }
103
+ updateIssue(projectId, issueRef, status) {
104
+ return this.request("PATCH", `/projects/${enc(projectId)}/issues/${enc(issueRef)}`, { body: { status } });
105
+ }
106
+ bulkUpdateIssues(projectId, ids, status) {
107
+ return this.request("POST", `/projects/${enc(projectId)}/issues/bulk`, { body: { ids, status } });
108
+ }
109
+ listIssueEvents(projectId, issueRef, params = {}) {
110
+ return this.request("GET", `/projects/${enc(projectId)}/issues/${enc(issueRef)}/events`, { query: params });
111
+ }
112
+ listEvents(projectId, params = {}) {
113
+ return this.request("GET", `/projects/${enc(projectId)}/events`, { query: params });
114
+ }
115
+ getEvent(projectId, eventId) {
116
+ return this.request("GET", `/projects/${enc(projectId)}/events/${enc(eventId)}`);
117
+ }
118
+ // --- Stats ------------------------------------------------------------- //
119
+ stats(projectId, params = {}) {
120
+ return this.request("GET", `/projects/${enc(projectId)}/stats`, { query: params });
121
+ }
122
+ live(projectId) {
123
+ return this.request("GET", `/projects/${enc(projectId)}/live`);
124
+ }
125
+ // --- Account ----------------------------------------------------------- //
126
+ usage() {
127
+ return this.request("GET", "/account/usage");
128
+ }
129
+ auditLog(params = {}) {
130
+ return this.request("GET", "/account/audit", { query: params });
131
+ }
132
+ // --- Ingest (project public key, no API key) ---------------------------- //
133
+ /**
134
+ * Report one event into a project. Authenticated with the project's PUBLIC
135
+ * key (`X-Project-Key`), never the API key: that is the only credential the
136
+ * ingest endpoint understands. Always 202; `ok: false` carries a drop reason.
137
+ */
138
+ ingest(publicKey, event) {
139
+ return this.request("POST", "/ingest", {
140
+ body: event,
141
+ auth: false,
142
+ headers: { "X-Project-Key": publicKey },
143
+ });
144
+ }
145
+ }
146
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AA6BA,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAEvC,uFAAuF;AACvF,MAAM,OAAO,YAAa,SAAQ,KAAK;IAEnB;IACA;IAGA;IALlB,YACkB,MAAc,EACd,IAAY,EAC5B,OAAe;IACf,qGAAqG;IACrF,QAAiC,EAAE;QAEnD,KAAK,CAAC,OAAO,CAAC,CAAC;QANC,WAAM,GAAN,MAAM,CAAQ;QACd,SAAI,GAAJ,IAAI,CAAQ;QAGZ,UAAK,GAAL,KAAK,CAA8B;QAGnD,IAAI,CAAC,IAAI,GAAG,cAAc,CAAC;IAC7B,CAAC;CACF;AAWD,MAAM,GAAG,GAAG,kBAAkB,CAAC;AAE/B,MAAM,OAAO,UAAU;IACJ,OAAO,CAAS;IAChB,MAAM,CAAS;IACf,SAAS,CAAe;IAEzC,YAAY,IAAuB;QACjC,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;QAChD,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC;QAC1B,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,KAAK,IAAI,UAAU,CAAC,KAAK,CAAC;IAClD,CAAC;IAEO,KAAK,CAAC,OAAO,CACnB,MAAc,EACd,IAAY,EACZ,OAA4F,EAAE;QAE9F,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,CAAC;QACzC,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC,EAAE,CAAC;YACtD,IAAI,CAAC,KAAK,SAAS,IAAI,CAAC,KAAK,EAAE;gBAAE,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;QACtE,CAAC;QACD,MAAM,OAAO,GAA2B;YACtC,MAAM,EAAE,kBAAkB;YAC1B,YAAY,EAAE,mBAAmB,OAAO,EAAE;YAC1C,GAAG,CAAC,IAAI,CAAC,OAAO,IAAI,EAAE,CAAC;SACxB,CAAC;QACF,IAAI,IAAI,CAAC,IAAI,KAAK,KAAK;YAAE,OAAO,CAAC,WAAW,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC;QAC5D,IAAI,IAAI,CAAC,IAAI,KAAK,SAAS;YAAE,OAAO,CAAC,cAAc,CAAC,GAAG,kBAAkB,CAAC;QAE1E,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,GAAG,EAAE;YACpC,MAAM;YACN,OAAO;YACP,IAAI,EAAE,IAAI,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC;SACtE,CAAC,CAAC;QAEH,IAAI,GAAG,CAAC,MAAM,KAAK,GAAG;YAAE,OAAO,SAAc,CAAC;QAC9C,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;QAC9B,IAAI,IAAI,GAAY,IAAI,CAAC;QACzB,IAAI,CAAC;YACH,IAAI,GAAG,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;QACxC,CAAC;QAAC,MAAM,CAAC;YACP,IAAI,GAAG,IAAI,CAAC;QACd,CAAC;QACD,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;YACZ,MAAM,GAAG,GAAG,CAAE,IAAmD,EAAE,KAAK,IAAI,EAAE,CAG7E,CAAC;YACF,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,KAAK,EAAE,GAAG,GAAG,CAAC;YACxC,MAAM,IAAI,YAAY,CACpB,GAAG,CAAC,MAAM,EACV,OAAO,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,GAAG,CAAC,MAAM,EAAE,EACtD,OAAO,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,QAAQ,GAAG,CAAC,MAAM,EAAE,EAC5D,KAAK,CACN,CAAC;QACJ,CAAC;QACD,OAAO,IAAS,CAAC;IACnB,CAAC;IAED,4CAA4C;IAC5C,EAAE;QACA,OAAO,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,aAAa,CAAC,CAAC;IAC5C,CAAC;IAED,6EAA6E;IAE7E,YAAY;QACV,OAAO,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,WAAW,CAAC,CAAC;IAC1C,CAAC;IAED,aAAa,CAAC,KAAyB;QACrC,OAAO,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,WAAW,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IAC5D,CAAC;IAED,UAAU,CAAC,EAAU;QACnB,OAAO,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,aAAa,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;IACrD,CAAC;IAED,aAAa,CAAC,EAAU,EAAE,KAAyB;QACjD,OAAO,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,aAAa,GAAG,CAAC,EAAE,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IACxE,CAAC;IAED,aAAa,CAAC,EAAU;QACtB,OAAO,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,aAAa,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;IACxD,CAAC;IAED,sFAAsF;IACtF,eAAe,CAAC,EAAU;QACxB,OAAO,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,aAAa,GAAG,CAAC,EAAE,CAAC,aAAa,CAAC,CAAC;IACjE,CAAC;IAED,OAAO,CAAC,SAAiB;QACvB,OAAO,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,aAAa,GAAG,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC;IACpE,CAAC;IAED,kDAAkD;IAClD,WAAW,CAAC,SAAiB;QAC3B,OAAO,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,aAAa,GAAG,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC;IACtE,CAAC;IAED,+EAA+E;IAE/E,UAAU,CAAC,SAAiB,EAAE,SAAuB,EAAE;QACrD,OAAO,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,aAAa,GAAG,CAAC,SAAS,CAAC,SAAS,EAAE,EAAE,KAAK,EAAE,MAAe,EAAE,CAAC,CAAC;IAC/F,CAAC;IAED,gGAAgG;IAChG,QAAQ,CAAC,SAAiB,EAAE,QAAgB;QAC1C,OAAO,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,aAAa,GAAG,CAAC,SAAS,CAAC,WAAW,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;IACpF,CAAC;IAED,WAAW,CAAC,SAAiB,EAAE,QAAgB,EAAE,MAAmB;QAClE,OAAO,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,aAAa,GAAG,CAAC,SAAS,CAAC,WAAW,GAAG,CAAC,QAAQ,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,MAAM,EAAE,EAAE,CAAC,CAAC;IAC5G,CAAC;IAED,gBAAgB,CAAC,SAAiB,EAAE,GAAa,EAAE,MAAmB;QACpE,OAAO,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,aAAa,GAAG,CAAC,SAAS,CAAC,cAAc,EAAE,EAAE,IAAI,EAAE,EAAE,GAAG,EAAE,MAAM,EAAE,EAAE,CAAC,CAAC;IACpG,CAAC;IAED,eAAe,CAAC,SAAiB,EAAE,QAAgB,EAAE,SAA+C,EAAE;QACpG,OAAO,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,aAAa,GAAG,CAAC,SAAS,CAAC,WAAW,GAAG,CAAC,QAAQ,CAAC,SAAS,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAC;IAC9G,CAAC;IAED,UAAU,CAAC,SAAiB,EAAE,SAAuB,EAAE;QACrD,OAAO,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,aAAa,GAAG,CAAC,SAAS,CAAC,SAAS,EAAE,EAAE,KAAK,EAAE,MAAe,EAAE,CAAC,CAAC;IAC/F,CAAC;IAED,QAAQ,CAAC,SAAiB,EAAE,OAAe;QACzC,OAAO,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,aAAa,GAAG,CAAC,SAAS,CAAC,WAAW,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;IACnF,CAAC;IAED,6EAA6E;IAE7E,KAAK,CAAC,SAAiB,EAAE,SAAsB,EAAE;QAC/C,OAAO,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,aAAa,GAAG,CAAC,SAAS,CAAC,QAAQ,EAAE,EAAE,KAAK,EAAE,MAAe,EAAE,CAAC,CAAC;IAC9F,CAAC;IAED,IAAI,CAAC,SAAiB;QACpB,OAAO,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,aAAa,GAAG,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;IACjE,CAAC;IAED,6EAA6E;IAE7E,KAAK;QACH,OAAO,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,gBAAgB,CAAC,CAAC;IAC/C,CAAC;IAED,QAAQ,CAAC,SAAsB,EAAE;QAC/B,OAAO,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,gBAAgB,EAAE,EAAE,KAAK,EAAE,MAAe,EAAE,CAAC,CAAC;IAC3E,CAAC;IAED,8EAA8E;IAE9E;;;;OAIG;IACH,MAAM,CAAC,SAAiB,EAAE,KAAuB;QAC/C,OAAO,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,SAAS,EAAE;YACrC,IAAI,EAAE,KAAK;YACX,IAAI,EAAE,KAAK;YACX,OAAO,EAAE,EAAE,eAAe,EAAE,SAAS,EAAE;SACxC,CAAC,CAAC;IACL,CAAC;CACF"}
@@ -0,0 +1,9 @@
1
+ /** Environment-driven configuration shared by the stdio and HTTP entrypoints. */
2
+ export interface Config {
3
+ apiUrl: string;
4
+ apiKey: string | undefined;
5
+ auditLog: string | undefined;
6
+ port: number;
7
+ }
8
+ export declare const DEFAULT_API_URL = "https://oops.3m4.net/api";
9
+ export declare function loadConfig(env?: NodeJS.ProcessEnv): Config;
package/dist/config.js ADDED
@@ -0,0 +1,11 @@
1
+ /** Environment-driven configuration shared by the stdio and HTTP entrypoints. */
2
+ export const DEFAULT_API_URL = "https://oops.3m4.net/api";
3
+ export function loadConfig(env = process.env) {
4
+ return {
5
+ apiUrl: (env.OOPS_API_URL || DEFAULT_API_URL).replace(/\/+$/, ""),
6
+ apiKey: env.OOPS_API_KEY?.trim() || undefined,
7
+ auditLog: env.OOPS_AUDIT_LOG,
8
+ port: Number(env.OOPS_MCP_PORT || 3333),
9
+ };
10
+ }
11
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,iFAAiF;AASjF,MAAM,CAAC,MAAM,eAAe,GAAG,0BAA0B,CAAC;AAE1D,MAAM,UAAU,UAAU,CAAC,MAAyB,OAAO,CAAC,GAAG;IAC7D,OAAO;QACL,MAAM,EAAE,CAAC,GAAG,CAAC,YAAY,IAAI,eAAe,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC;QACjE,MAAM,EAAE,GAAG,CAAC,YAAY,EAAE,IAAI,EAAE,IAAI,SAAS;QAC7C,QAAQ,EAAE,GAAG,CAAC,cAAc;QAC5B,IAAI,EAAE,MAAM,CAAC,GAAG,CAAC,aAAa,IAAI,IAAI,CAAC;KACxC,CAAC;AACJ,CAAC"}
package/dist/http.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/http.js ADDED
@@ -0,0 +1,77 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Streamable HTTP entrypoint (remote MCP).
4
+ *
5
+ * - One endpoint, `POST /mcp`. Auth is the oops API key as a Bearer token
6
+ * (`Authorization: Bearer oops_live_…`); `X-API-Key` is accepted too.
7
+ * - Stateless: every request gets a fresh server + transport bound to the
8
+ * caller's key, so tenants can never share state. The cost is one
9
+ * `/apikeys/me` lookup per request, cached briefly per key.
10
+ * - No elicitation here: a stateless server has no back-channel to the user,
11
+ * so the destructive tools fall back to the explicit `confirm=true` flow.
12
+ */
13
+ import { createHash } from "node:crypto";
14
+ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
15
+ import express from "express";
16
+ import { createAuditSink } from "./audit.js";
17
+ import { OopsApiError, OopsClient } from "./client.js";
18
+ import { loadConfig } from "./config.js";
19
+ import { createOopsServer } from "./server.js";
20
+ import { VERSION } from "./version.js";
21
+ const cfg = loadConfig();
22
+ const audit = createAuditSink(cfg.auditLog);
23
+ const ME_CACHE_MS = 60_000;
24
+ const meCache = new Map();
25
+ function extractKey(req) {
26
+ const auth = req.header("authorization");
27
+ if (auth?.toLowerCase().startsWith("bearer "))
28
+ return auth.slice(7).trim();
29
+ return req.header("x-api-key")?.trim() || undefined;
30
+ }
31
+ async function resolveKey(apiKey) {
32
+ const id = createHash("sha256").update(apiKey).digest("hex");
33
+ const hit = meCache.get(id);
34
+ if (hit && hit.expires > Date.now())
35
+ return hit.info;
36
+ const info = await new OopsClient({ baseUrl: cfg.apiUrl, apiKey }).me();
37
+ meCache.set(id, { info, expires: Date.now() + ME_CACHE_MS });
38
+ return info;
39
+ }
40
+ function rpcError(res, status, message) {
41
+ if (status === 401)
42
+ res.setHeader("WWW-Authenticate", 'Bearer realm="oops-errors-mcp"');
43
+ res.status(status).json({ jsonrpc: "2.0", error: { code: -32000, message }, id: null });
44
+ }
45
+ const app = express();
46
+ app.use(express.json({ limit: "1mb" }));
47
+ app.get("/healthz", (_req, res) => {
48
+ res.json({ status: "ok", name: "oops-errors-mcp", version: VERSION });
49
+ });
50
+ app.all("/mcp", async (req, res) => {
51
+ const apiKey = extractKey(req);
52
+ if (!apiKey)
53
+ return rpcError(res, 401, "Missing API key. Send `Authorization: Bearer oops_live_…`.");
54
+ let keyInfo;
55
+ try {
56
+ keyInfo = await resolveKey(apiKey);
57
+ }
58
+ catch (err) {
59
+ if (err instanceof OopsApiError && (err.status === 401 || err.status === 403)) {
60
+ return rpcError(res, err.status, err.message);
61
+ }
62
+ return rpcError(res, 502, `Upstream oops API unavailable: ${err instanceof Error ? err.message : String(err)}`);
63
+ }
64
+ const client = new OopsClient({ baseUrl: cfg.apiUrl, apiKey });
65
+ const server = createOopsServer({ client, keyInfo, audit, transport: "http", allowElicitation: false });
66
+ const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined });
67
+ res.on("close", () => {
68
+ void transport.close();
69
+ void server.close();
70
+ });
71
+ await server.connect(transport);
72
+ await transport.handleRequest(req, res, req.body);
73
+ });
74
+ app.listen(cfg.port, "0.0.0.0", () => {
75
+ process.stderr.write(`oops-errors-mcp http listening on :${cfg.port} (api: ${cfg.apiUrl})\n`);
76
+ });
77
+ //# sourceMappingURL=http.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http.js","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ export { createOopsServer, buildInstructions, type OopsServerOptions } from "./server.js";
2
+ export { OopsClient, OopsApiError, type OopsClientOptions } from "./client.js";
3
+ export { TOOL_SCOPES, ALL_TOOLS, CONFIRMED_TOOLS, allowedTools, hasScope, hasAllScopes, type ToolName } from "./rbac.js";
4
+ export { parseIssueRef, formatIssueRef, InvalidIssueRef } from "./issue.js";
5
+ export { createAuditSink, StderrAuditSink, FileAuditSink, MemoryAuditSink, NullAuditSink, type AuditSink, type AuditEntry, type AuditOutcome, } from "./audit.js";
6
+ export { loadConfig, DEFAULT_API_URL, type Config } from "./config.js";
7
+ export type * from "./types.js";
8
+ export { VERSION } from "./version.js";
package/dist/index.js ADDED
@@ -0,0 +1,8 @@
1
+ export { createOopsServer, buildInstructions } from "./server.js";
2
+ export { OopsClient, OopsApiError } from "./client.js";
3
+ export { TOOL_SCOPES, ALL_TOOLS, CONFIRMED_TOOLS, allowedTools, hasScope, hasAllScopes } from "./rbac.js";
4
+ export { parseIssueRef, formatIssueRef, InvalidIssueRef } from "./issue.js";
5
+ export { createAuditSink, StderrAuditSink, FileAuditSink, MemoryAuditSink, NullAuditSink, } from "./audit.js";
6
+ export { loadConfig, DEFAULT_API_URL } from "./config.js";
7
+ export { VERSION } from "./version.js";
8
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAA0B,MAAM,aAAa,CAAC;AAC1F,OAAO,EAAE,UAAU,EAAE,YAAY,EAA0B,MAAM,aAAa,CAAC;AAC/E,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,eAAe,EAAE,YAAY,EAAE,QAAQ,EAAE,YAAY,EAAiB,MAAM,WAAW,CAAC;AACzH,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAC5E,OAAO,EACL,eAAe,EACf,eAAe,EACf,aAAa,EACb,eAAe,EACf,aAAa,GAId,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,UAAU,EAAE,eAAe,EAAe,MAAM,aAAa,CAAC;AAEvE,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC"}