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 +175 -2
- package/dist/audit.d.ts +42 -0
- package/dist/audit.js +53 -0
- package/dist/audit.js.map +1 -0
- package/dist/client.d.ts +68 -0
- package/dist/client.js +146 -0
- package/dist/client.js.map +1 -0
- package/dist/config.d.ts +9 -0
- package/dist/config.js +11 -0
- package/dist/config.js.map +1 -0
- package/dist/http.d.ts +2 -0
- package/dist/http.js +77 -0
- package/dist/http.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/issue.d.ts +17 -0
- package/dist/issue.js +42 -0
- package/dist/issue.js.map +1 -0
- package/dist/rbac.d.ts +47 -0
- package/dist/rbac.js +45 -0
- package/dist/rbac.js.map +1 -0
- package/dist/server.d.ts +42 -0
- package/dist/server.js +813 -0
- package/dist/server.js.map +1 -0
- package/dist/stdio.d.ts +2 -0
- package/dist/stdio.js +45 -0
- package/dist/stdio.js.map +1 -0
- package/dist/types.d.ts +294 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/dist/version.d.ts +1 -0
- package/dist/version.js +2 -0
- package/dist/version.js.map +1 -0
- package/package.json +55 -4
package/dist/issue.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Issue references as agents and humans write them.
|
|
3
|
+
*
|
|
4
|
+
* The REST API accepts either the issue uuid or its per-project number in the
|
|
5
|
+
* `<issue_id>` path segment, so the server only has to normalise what the
|
|
6
|
+
* model passed: `12`, `"12"`, `"#12"` and `" # 12 "` all become `"12"`; a uuid
|
|
7
|
+
* passes through unchanged (lower-cased); anything else is rejected before a
|
|
8
|
+
* request is made.
|
|
9
|
+
*/
|
|
10
|
+
export declare class InvalidIssueRef extends Error {
|
|
11
|
+
readonly input: unknown;
|
|
12
|
+
constructor(input: unknown);
|
|
13
|
+
}
|
|
14
|
+
/** Returns the path segment to use with the API (`"12"` or the uuid). */
|
|
15
|
+
export declare function parseIssueRef(input: string | number): string;
|
|
16
|
+
/** `#12` for numbers, the uuid otherwise: how the reference is echoed back in summaries. */
|
|
17
|
+
export declare function formatIssueRef(ref: string): string;
|
package/dist/issue.js
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Issue references as agents and humans write them.
|
|
3
|
+
*
|
|
4
|
+
* The REST API accepts either the issue uuid or its per-project number in the
|
|
5
|
+
* `<issue_id>` path segment, so the server only has to normalise what the
|
|
6
|
+
* model passed: `12`, `"12"`, `"#12"` and `" # 12 "` all become `"12"`; a uuid
|
|
7
|
+
* passes through unchanged (lower-cased); anything else is rejected before a
|
|
8
|
+
* request is made.
|
|
9
|
+
*/
|
|
10
|
+
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
11
|
+
const NUMBER_RE = /^#?\s*(\d{1,9})$/;
|
|
12
|
+
export class InvalidIssueRef extends Error {
|
|
13
|
+
input;
|
|
14
|
+
constructor(input) {
|
|
15
|
+
super(`Invalid issue reference ${JSON.stringify(input)}: pass the issue uuid, its number (12) or "#12".`);
|
|
16
|
+
this.input = input;
|
|
17
|
+
this.name = "InvalidIssueRef";
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
/** Returns the path segment to use with the API (`"12"` or the uuid). */
|
|
21
|
+
export function parseIssueRef(input) {
|
|
22
|
+
if (typeof input === "number") {
|
|
23
|
+
if (!Number.isInteger(input) || input < 1)
|
|
24
|
+
throw new InvalidIssueRef(input);
|
|
25
|
+
return String(input);
|
|
26
|
+
}
|
|
27
|
+
const s = input.trim();
|
|
28
|
+
if (UUID_RE.test(s))
|
|
29
|
+
return s.toLowerCase();
|
|
30
|
+
const m = NUMBER_RE.exec(s);
|
|
31
|
+
if (m) {
|
|
32
|
+
const n = Number(m[1]);
|
|
33
|
+
if (n >= 1)
|
|
34
|
+
return String(n);
|
|
35
|
+
}
|
|
36
|
+
throw new InvalidIssueRef(input);
|
|
37
|
+
}
|
|
38
|
+
/** `#12` for numbers, the uuid otherwise: how the reference is echoed back in summaries. */
|
|
39
|
+
export function formatIssueRef(ref) {
|
|
40
|
+
return /^\d+$/.test(ref) ? `#${ref}` : ref;
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=issue.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"issue.js","sourceRoot":"","sources":["../src/issue.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,MAAM,OAAO,GAAG,iEAAiE,CAAC;AAClF,MAAM,SAAS,GAAG,kBAAkB,CAAC;AAErC,MAAM,OAAO,eAAgB,SAAQ,KAAK;IACZ;IAA5B,YAA4B,KAAc;QACxC,KAAK,CAAC,2BAA2B,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,kDAAkD,CAAC,CAAC;QADhF,UAAK,GAAL,KAAK,CAAS;QAExC,IAAI,CAAC,IAAI,GAAG,iBAAiB,CAAC;IAChC,CAAC;CACF;AAED,yEAAyE;AACzE,MAAM,UAAU,aAAa,CAAC,KAAsB;IAClD,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC9B,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,KAAK,GAAG,CAAC;YAAE,MAAM,IAAI,eAAe,CAAC,KAAK,CAAC,CAAC;QAC5E,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC;IACvB,CAAC;IACD,MAAM,CAAC,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC;IACvB,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;QAAE,OAAO,CAAC,CAAC,WAAW,EAAE,CAAC;IAC5C,MAAM,CAAC,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAC5B,IAAI,CAAC,EAAE,CAAC;QACN,MAAM,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACvB,IAAI,CAAC,IAAI,CAAC;YAAE,OAAO,MAAM,CAAC,CAAC,CAAC,CAAC;IAC/B,CAAC;IACD,MAAM,IAAI,eAAe,CAAC,KAAK,CAAC,CAAC;AACnC,CAAC;AAED,4FAA4F;AAC5F,MAAM,UAAU,cAAc,CAAC,GAAW;IACxC,OAAO,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,GAAG,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC;AAC7C,CAAC"}
|
package/dist/rbac.d.ts
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool-level authorization.
|
|
3
|
+
*
|
|
4
|
+
* Every tool declares the scope(s) it needs. The server consults this table
|
|
5
|
+
* twice (defense in depth):
|
|
6
|
+
*
|
|
7
|
+
* 1. at registration — tools the key cannot use are never listed, so the
|
|
8
|
+
* model is not tempted to call them;
|
|
9
|
+
* 2. at call time — a tool that is somehow invoked without its scope is
|
|
10
|
+
* refused and the refusal is audited.
|
|
11
|
+
*
|
|
12
|
+
* The REST API enforces the same scopes independently (layer 3), so a bug in
|
|
13
|
+
* this file can never *grant* access, only hide it.
|
|
14
|
+
*
|
|
15
|
+
* The table mirrors the "MCP server" section of CONTRACT.md; the roles that
|
|
16
|
+
* grant each scope live in packages/shared/api_key_roles.json.
|
|
17
|
+
*/
|
|
18
|
+
import type { Scope } from "./types.js";
|
|
19
|
+
export declare const TOOL_SCOPES: {
|
|
20
|
+
readonly list_projects: readonly ["projects:read"];
|
|
21
|
+
readonly get_project: readonly ["projects:read"];
|
|
22
|
+
readonly create_project: readonly ["projects:write"];
|
|
23
|
+
readonly update_project: readonly ["projects:write"];
|
|
24
|
+
readonly rotate_public_key: readonly ["projects:write"];
|
|
25
|
+
readonly delete_project: readonly ["projects:delete"];
|
|
26
|
+
readonly list_issues: readonly ["issues:read"];
|
|
27
|
+
readonly get_issue: readonly ["issues:read"];
|
|
28
|
+
readonly list_issue_events: readonly ["issues:read"];
|
|
29
|
+
readonly get_event: readonly ["issues:read"];
|
|
30
|
+
readonly search_events: readonly ["issues:read"];
|
|
31
|
+
readonly update_issue: readonly ["issues:write"];
|
|
32
|
+
readonly bulk_update_issues: readonly ["issues:write"];
|
|
33
|
+
readonly get_stats: readonly ["issues:read"];
|
|
34
|
+
readonly get_live: readonly ["issues:read"];
|
|
35
|
+
readonly purge_events: readonly ["events:delete"];
|
|
36
|
+
readonly get_usage: readonly ["usage:read"];
|
|
37
|
+
readonly get_audit_log: readonly ["audit:read"];
|
|
38
|
+
readonly report_error: readonly ["projects:read"];
|
|
39
|
+
};
|
|
40
|
+
export type ToolName = keyof typeof TOOL_SCOPES;
|
|
41
|
+
export declare const ALL_TOOLS: ToolName[];
|
|
42
|
+
/** Tools that never run without explicit user confirmation. */
|
|
43
|
+
export declare const CONFIRMED_TOOLS: readonly ToolName[];
|
|
44
|
+
export declare function hasScope(scopes: Iterable<Scope>, scope: Scope): boolean;
|
|
45
|
+
/** True when every scope the tool needs is granted. */
|
|
46
|
+
export declare function hasAllScopes(scopes: Iterable<Scope>, required: readonly Scope[]): boolean;
|
|
47
|
+
export declare function allowedTools(scopes: Iterable<Scope>): ToolName[];
|
package/dist/rbac.js
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
export const TOOL_SCOPES = {
|
|
2
|
+
// projects
|
|
3
|
+
list_projects: ["projects:read"],
|
|
4
|
+
get_project: ["projects:read"],
|
|
5
|
+
create_project: ["projects:write"],
|
|
6
|
+
update_project: ["projects:write"],
|
|
7
|
+
rotate_public_key: ["projects:write"],
|
|
8
|
+
delete_project: ["projects:delete"],
|
|
9
|
+
// issues and events
|
|
10
|
+
list_issues: ["issues:read"],
|
|
11
|
+
get_issue: ["issues:read"],
|
|
12
|
+
list_issue_events: ["issues:read"],
|
|
13
|
+
get_event: ["issues:read"],
|
|
14
|
+
search_events: ["issues:read"],
|
|
15
|
+
update_issue: ["issues:write"],
|
|
16
|
+
bulk_update_issues: ["issues:write"],
|
|
17
|
+
// stats
|
|
18
|
+
get_stats: ["issues:read"],
|
|
19
|
+
get_live: ["issues:read"],
|
|
20
|
+
purge_events: ["events:delete"],
|
|
21
|
+
// account
|
|
22
|
+
get_usage: ["usage:read"],
|
|
23
|
+
get_audit_log: ["audit:read"],
|
|
24
|
+
// reporting: needs to read the project's public key, then posts to /ingest
|
|
25
|
+
report_error: ["projects:read"],
|
|
26
|
+
};
|
|
27
|
+
export const ALL_TOOLS = Object.keys(TOOL_SCOPES);
|
|
28
|
+
/** Tools that never run without explicit user confirmation. */
|
|
29
|
+
export const CONFIRMED_TOOLS = ["delete_project", "purge_events"];
|
|
30
|
+
export function hasScope(scopes, scope) {
|
|
31
|
+
for (const s of scopes)
|
|
32
|
+
if (s === scope)
|
|
33
|
+
return true;
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
/** True when every scope the tool needs is granted. */
|
|
37
|
+
export function hasAllScopes(scopes, required) {
|
|
38
|
+
const set = new Set(scopes);
|
|
39
|
+
return required.every((s) => set.has(s));
|
|
40
|
+
}
|
|
41
|
+
export function allowedTools(scopes) {
|
|
42
|
+
const set = new Set(scopes);
|
|
43
|
+
return ALL_TOOLS.filter((tool) => TOOL_SCOPES[tool].every((s) => set.has(s)));
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=rbac.js.map
|
package/dist/rbac.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rbac.js","sourceRoot":"","sources":["../src/rbac.ts"],"names":[],"mappings":"AAmBA,MAAM,CAAC,MAAM,WAAW,GAAG;IACzB,WAAW;IACX,aAAa,EAAE,CAAC,eAAe,CAAC;IAChC,WAAW,EAAE,CAAC,eAAe,CAAC;IAC9B,cAAc,EAAE,CAAC,gBAAgB,CAAC;IAClC,cAAc,EAAE,CAAC,gBAAgB,CAAC;IAClC,iBAAiB,EAAE,CAAC,gBAAgB,CAAC;IACrC,cAAc,EAAE,CAAC,iBAAiB,CAAC;IACnC,oBAAoB;IACpB,WAAW,EAAE,CAAC,aAAa,CAAC;IAC5B,SAAS,EAAE,CAAC,aAAa,CAAC;IAC1B,iBAAiB,EAAE,CAAC,aAAa,CAAC;IAClC,SAAS,EAAE,CAAC,aAAa,CAAC;IAC1B,aAAa,EAAE,CAAC,aAAa,CAAC;IAC9B,YAAY,EAAE,CAAC,cAAc,CAAC;IAC9B,kBAAkB,EAAE,CAAC,cAAc,CAAC;IACpC,QAAQ;IACR,SAAS,EAAE,CAAC,aAAa,CAAC;IAC1B,QAAQ,EAAE,CAAC,aAAa,CAAC;IACzB,YAAY,EAAE,CAAC,eAAe,CAAC;IAC/B,UAAU;IACV,SAAS,EAAE,CAAC,YAAY,CAAC;IACzB,aAAa,EAAE,CAAC,YAAY,CAAC;IAC7B,2EAA2E;IAC3E,YAAY,EAAE,CAAC,eAAe,CAAC;CACoB,CAAC;AAItD,MAAM,CAAC,MAAM,SAAS,GAAG,MAAM,CAAC,IAAI,CAAC,WAAW,CAAe,CAAC;AAEhE,+DAA+D;AAC/D,MAAM,CAAC,MAAM,eAAe,GAAwB,CAAC,gBAAgB,EAAE,cAAc,CAAC,CAAC;AAEvF,MAAM,UAAU,QAAQ,CAAC,MAAuB,EAAE,KAAY;IAC5D,KAAK,MAAM,CAAC,IAAI,MAAM;QAAE,IAAI,CAAC,KAAK,KAAK;YAAE,OAAO,IAAI,CAAC;IACrD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,uDAAuD;AACvD,MAAM,UAAU,YAAY,CAAC,MAAuB,EAAE,QAA0B;IAC9E,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,CAAC;IAC5B,OAAO,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;AAC3C,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,MAAuB;IAClD,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,CAAC;IAC5B,OAAO,SAAS,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAChF,CAAC"}
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The oops MCP server: tools, resources and prompts over the oops management
|
|
3
|
+
* API (plus the ingest endpoint for `report_error`).
|
|
4
|
+
*
|
|
5
|
+
* Design notes (the parts worth reading before changing anything):
|
|
6
|
+
*
|
|
7
|
+
* - **RBAC, three layers.** The key's scopes (from `GET /apikeys/me`) decide
|
|
8
|
+
* which tools are *registered* (hidden tools are never listed), each handler
|
|
9
|
+
* re-checks its scopes at call time, and the REST API enforces the same
|
|
10
|
+
* scopes independently. See `rbac.ts`.
|
|
11
|
+
* - **Human approval for destructive actions.** `delete_project` and
|
|
12
|
+
* `purge_events` ask the user through MCP *elicitation* when the client
|
|
13
|
+
* supports it (Claude Code, the Inspector). Otherwise they refuse until the
|
|
14
|
+
* model passes `confirm: true`, which forces the model to ask the user first.
|
|
15
|
+
* - **Structured outputs.** Every tool declares an `outputSchema` and returns
|
|
16
|
+
* `structuredContent`, so hosts can validate and chain results without
|
|
17
|
+
* parsing prose. Objects that mirror API responses are *loose* (extra fields
|
|
18
|
+
* allowed) so an additive API change never breaks client-side validation.
|
|
19
|
+
* The text content is a compact, human/agent-readable summary.
|
|
20
|
+
* - **Errors are results, not exceptions.** API errors come back as
|
|
21
|
+
* `isError: true` with the stable error code, so the model can recover
|
|
22
|
+
* (e.g. `limit_reached` → tell the user to upgrade) instead of crashing.
|
|
23
|
+
* - **Everything is audited**, locally (`audit.ts`) and server-side.
|
|
24
|
+
*/
|
|
25
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
26
|
+
import { type AuditSink } from "./audit.js";
|
|
27
|
+
import { type OopsClient } from "./client.js";
|
|
28
|
+
import type { KeyInfo } from "./types.js";
|
|
29
|
+
export interface OopsServerOptions {
|
|
30
|
+
client: OopsClient;
|
|
31
|
+
keyInfo: KeyInfo;
|
|
32
|
+
audit: AuditSink;
|
|
33
|
+
/** Label written to the audit log: "stdio" | "http". */
|
|
34
|
+
transport?: string;
|
|
35
|
+
/**
|
|
36
|
+
* Whether destructive tools may ask the user via elicitation. Only safe on
|
|
37
|
+
* a transport with a persistent back-channel (stdio). Default: true.
|
|
38
|
+
*/
|
|
39
|
+
allowElicitation?: boolean;
|
|
40
|
+
}
|
|
41
|
+
export declare function buildInstructions(info: KeyInfo): string;
|
|
42
|
+
export declare function createOopsServer(opts: OopsServerOptions): McpServer;
|