@myna-sh/mcp 0.12.2 → 0.14.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/dist/main.d.ts +12 -0
- package/dist/main.js +450 -2
- package/dist/main.js.map +1 -1
- package/package.json +3 -3
package/dist/main.d.ts
CHANGED
|
@@ -89,6 +89,18 @@ interface ToolOptions {
|
|
|
89
89
|
* remote client must never be able to read the server's filesystem.
|
|
90
90
|
*/
|
|
91
91
|
allowPathUploads: boolean;
|
|
92
|
+
/**
|
|
93
|
+
* Myna products the target project runs.
|
|
94
|
+
*
|
|
95
|
+
* Tools for a product are registered only when the project has it enabled.
|
|
96
|
+
* Omitting this registers the Content tools and nothing else — the
|
|
97
|
+
* conservative default, and the one that matters: a customer's saved agent
|
|
98
|
+
* configuration must not change shape because Myna shipped a second product.
|
|
99
|
+
* There are already 45+ tools on this server, and every one added degrades
|
|
100
|
+
* selection for all of them, so a project that does not run Feedback should
|
|
101
|
+
* never be shown Feedback's.
|
|
102
|
+
*/
|
|
103
|
+
products?: readonly string[];
|
|
92
104
|
}
|
|
93
105
|
|
|
94
106
|
declare const SERVER_NAME = "myna";
|
package/dist/main.js
CHANGED
|
@@ -294,6 +294,45 @@ var ManagementClient = class {
|
|
|
294
294
|
mutate(method, path, body, query) {
|
|
295
295
|
return this.http.request(method, path, { body, query, idempotencyKey: this.idem() });
|
|
296
296
|
}
|
|
297
|
+
/**
|
|
298
|
+
* Escape hatch: call any management endpoint, wrapped or not.
|
|
299
|
+
*
|
|
300
|
+
* The typed methods above cover the API, but never all of it at once — a
|
|
301
|
+
* route ships before its wrapper, or a caller needs a header the wrapper does
|
|
302
|
+
* not take. Without a documented way around the client, that caller hand-rolls
|
|
303
|
+
* `fetch` and loses everything this class provides: credential handling,
|
|
304
|
+
* retry policy, and RFC 9457 errors thrown as `MynaApiError` rather than left
|
|
305
|
+
* as an opaque body to parse. The escape hatch keeps all of it.
|
|
306
|
+
*
|
|
307
|
+
* `path` is version-relative (`/projects/x`), and a leading `/v1` is accepted
|
|
308
|
+
* and stripped — this is the one method whose paths are written by hand, and
|
|
309
|
+
* every example a caller copies from the docs carries the prefix.
|
|
310
|
+
*
|
|
311
|
+
* Unlike the typed mutations, no idempotency key is generated: sending one
|
|
312
|
+
* makes a request retry-eligible, and only the caller knows whether replaying
|
|
313
|
+
* this particular one is safe. Pass `idempotencyKey` to opt in.
|
|
314
|
+
*
|
|
315
|
+
* Returns the response envelope untouched — `{ data, pagination }` and all —
|
|
316
|
+
* because an escape hatch that reshapes the response is not one.
|
|
317
|
+
*/
|
|
318
|
+
async raw(method, path, options = {}) {
|
|
319
|
+
const relative = path.startsWith("/v1/") ? path.slice(3) : path.startsWith("/") ? path : `/${path}`;
|
|
320
|
+
const response = await this.http.requestRaw(method, relative, options);
|
|
321
|
+
const headers = {};
|
|
322
|
+
response.headers.forEach((value, key) => {
|
|
323
|
+
headers[key] = value;
|
|
324
|
+
});
|
|
325
|
+
if (response.status === 204 || response.status === 304) {
|
|
326
|
+
return { status: response.status, headers, body: void 0 };
|
|
327
|
+
}
|
|
328
|
+
const text = await response.text();
|
|
329
|
+
if (!text) return { status: response.status, headers, body: void 0 };
|
|
330
|
+
try {
|
|
331
|
+
return { status: response.status, headers, body: JSON.parse(text) };
|
|
332
|
+
} catch {
|
|
333
|
+
return { status: response.status, headers, body: text };
|
|
334
|
+
}
|
|
335
|
+
}
|
|
297
336
|
// --- Organizations --------------------------------------------------------
|
|
298
337
|
organizations = {
|
|
299
338
|
list: (signal) => this.get("/organizations", void 0, signal),
|
|
@@ -497,6 +536,147 @@ var ManagementClient = class {
|
|
|
497
536
|
body
|
|
498
537
|
)
|
|
499
538
|
};
|
|
539
|
+
// --- Feedback: boards -----------------------------------------------------
|
|
540
|
+
/**
|
|
541
|
+
* Boards collect reports. A board's `formFields` is its intake form, declared
|
|
542
|
+
* with the same field DSL as a collection schema — so a QA board that needs a
|
|
543
|
+
* build number says so there, and gets the same validation as anything else
|
|
544
|
+
* in Myna.
|
|
545
|
+
*/
|
|
546
|
+
boards = {
|
|
547
|
+
list: (project, signal) => this.get(`/projects/${enc(project)}/boards`, void 0, signal),
|
|
548
|
+
get: (project, board, signal) => this.get(`/projects/${enc(project)}/boards/${enc(board)}`, void 0, signal),
|
|
549
|
+
create: (project, body) => this.mutate("POST", `/projects/${enc(project)}/boards`, body),
|
|
550
|
+
update: (project, board, body) => this.mutate("PATCH", `/projects/${enc(project)}/boards/${enc(board)}`, body),
|
|
551
|
+
delete: (project, board) => this.mutate("DELETE", `/projects/${enc(project)}/boards/${enc(board)}`)
|
|
552
|
+
};
|
|
553
|
+
// --- Feedback: reports ----------------------------------------------------
|
|
554
|
+
/**
|
|
555
|
+
* Bug reports, QA findings, feedback, and feature requests.
|
|
556
|
+
*
|
|
557
|
+
* `get` deliberately returns everything at once — body, board guidance,
|
|
558
|
+
* custom fields, developer-supplied context, the full timeline with resolved
|
|
559
|
+
* actors, the attachment manifest with short-lived URLs, and the links.
|
|
560
|
+
* Assembling a bug report from six calls is the problem this product exists
|
|
561
|
+
* to remove, so its own client does not make you do it.
|
|
562
|
+
*
|
|
563
|
+
* A report reference may be an id (`rep_…`), a number, or `#number`.
|
|
564
|
+
*/
|
|
565
|
+
reports = {
|
|
566
|
+
list: (project, opts = {}) => {
|
|
567
|
+
const { board, status, type, priority, assignee, labels, q, since, ...rest } = opts;
|
|
568
|
+
return this.page(`/projects/${enc(project)}/reports`, rest, {
|
|
569
|
+
board,
|
|
570
|
+
status: Array.isArray(status) ? status.join(",") : status,
|
|
571
|
+
type,
|
|
572
|
+
priority,
|
|
573
|
+
assignee,
|
|
574
|
+
labels: labels?.join(","),
|
|
575
|
+
q,
|
|
576
|
+
since
|
|
577
|
+
});
|
|
578
|
+
},
|
|
579
|
+
get: (project, report, signal) => this.get(
|
|
580
|
+
`/projects/${enc(project)}/reports/${enc(String(report))}`,
|
|
581
|
+
void 0,
|
|
582
|
+
signal
|
|
583
|
+
),
|
|
584
|
+
create: (project, body) => this.mutate("POST", `/projects/${enc(project)}/reports`, body),
|
|
585
|
+
update: (project, report, body) => this.mutate(
|
|
586
|
+
"PATCH",
|
|
587
|
+
`/projects/${enc(project)}/reports/${enc(String(report))}`,
|
|
588
|
+
body
|
|
589
|
+
),
|
|
590
|
+
comment: (project, report, body) => this.mutate(
|
|
591
|
+
"POST",
|
|
592
|
+
`/projects/${enc(project)}/reports/${enc(String(report))}/comments`,
|
|
593
|
+
body
|
|
594
|
+
),
|
|
595
|
+
resolve: (project, report, body = {}) => this.mutate(
|
|
596
|
+
"POST",
|
|
597
|
+
`/projects/${enc(project)}/reports/${enc(String(report))}/resolve`,
|
|
598
|
+
body
|
|
599
|
+
),
|
|
600
|
+
reopen: (project, report, body = {}) => this.mutate(
|
|
601
|
+
"POST",
|
|
602
|
+
`/projects/${enc(project)}/reports/${enc(String(report))}/reopen`,
|
|
603
|
+
body
|
|
604
|
+
),
|
|
605
|
+
/** Ask whoever reported it to confirm a fix. The human half of the loop. */
|
|
606
|
+
requestRetest: (project, report, body = {}) => this.mutate(
|
|
607
|
+
"POST",
|
|
608
|
+
`/projects/${enc(project)}/reports/${enc(String(report))}/retest`,
|
|
609
|
+
body
|
|
610
|
+
),
|
|
611
|
+
merge: (project, report, body) => this.mutate(
|
|
612
|
+
"POST",
|
|
613
|
+
`/projects/${enc(project)}/reports/${enc(String(report))}/merge`,
|
|
614
|
+
body
|
|
615
|
+
),
|
|
616
|
+
/**
|
|
617
|
+
* What has come in since a time or a release.
|
|
618
|
+
*
|
|
619
|
+
* The call to make before deciding what to work on: counts, groupings, and
|
|
620
|
+
* the reports themselves already ordered by priority then recency.
|
|
621
|
+
*/
|
|
622
|
+
digest: (project, opts = {}, signal) => this.get(
|
|
623
|
+
`/projects/${enc(project)}/reports/digest`,
|
|
624
|
+
{ since: opts.since, release: opts.release, limit: opts.limit },
|
|
625
|
+
signal
|
|
626
|
+
),
|
|
627
|
+
similar: (project, report, signal) => this.get(
|
|
628
|
+
`/projects/${enc(project)}/reports/${enc(String(report))}/similar`,
|
|
629
|
+
void 0,
|
|
630
|
+
signal
|
|
631
|
+
),
|
|
632
|
+
/** What publishing this report to a board would expose. Read before moving. */
|
|
633
|
+
exposure: (project, report, board, signal) => this.get(
|
|
634
|
+
`/projects/${enc(project)}/reports/${enc(String(report))}/exposure`,
|
|
635
|
+
{ board },
|
|
636
|
+
signal
|
|
637
|
+
),
|
|
638
|
+
/** Move between boards. A public destination needs `confirm: true`. */
|
|
639
|
+
move: (project, report, body) => this.mutate(
|
|
640
|
+
"POST",
|
|
641
|
+
`/projects/${enc(project)}/reports/${enc(String(report))}/move`,
|
|
642
|
+
body
|
|
643
|
+
),
|
|
644
|
+
link: (project, report, body) => this.mutate(
|
|
645
|
+
"POST",
|
|
646
|
+
`/projects/${enc(project)}/reports/${enc(String(report))}/links`,
|
|
647
|
+
body
|
|
648
|
+
),
|
|
649
|
+
/** One attachment, with a fresh short-lived URL. */
|
|
650
|
+
attachment: (project, report, attachment, signal) => this.get(
|
|
651
|
+
`/projects/${enc(project)}/reports/${enc(String(report))}/attachments/${enc(attachment)}`,
|
|
652
|
+
void 0,
|
|
653
|
+
signal
|
|
654
|
+
),
|
|
655
|
+
createUpload: (project, body) => this.mutate(
|
|
656
|
+
"POST",
|
|
657
|
+
`/projects/${enc(project)}/reports/attachments/uploads`,
|
|
658
|
+
body
|
|
659
|
+
),
|
|
660
|
+
attach: (project, report, uploadIds) => this.mutate(
|
|
661
|
+
"POST",
|
|
662
|
+
`/projects/${enc(project)}/reports/${enc(String(report))}/attachments`,
|
|
663
|
+
{ uploadIds }
|
|
664
|
+
)
|
|
665
|
+
};
|
|
666
|
+
// --- Feedback: ingest keys ------------------------------------------------
|
|
667
|
+
/**
|
|
668
|
+
* Publishable keys that let a website submit reports.
|
|
669
|
+
*
|
|
670
|
+
* Unlike every other credential here, one of these is *meant* to be readable
|
|
671
|
+
* by every visitor to a customer's page. What makes that safe lives on the
|
|
672
|
+
* server: the key may only file on one board, only from an origin the project
|
|
673
|
+
* registered, and only within its own rate and quota budget.
|
|
674
|
+
*/
|
|
675
|
+
ingestKeys = {
|
|
676
|
+
list: (project, signal) => this.get(`/projects/${enc(project)}/ingest-keys`, void 0, signal),
|
|
677
|
+
create: (project, body) => this.mutate("POST", `/projects/${enc(project)}/ingest-keys`, body),
|
|
678
|
+
revoke: (project, key) => this.mutate("DELETE", `/projects/${enc(project)}/ingest-keys/${enc(key)}`)
|
|
679
|
+
};
|
|
500
680
|
// --- Declared external checks ---------------------------------------------
|
|
501
681
|
checks = {
|
|
502
682
|
list: (project, signal) => this.get(`/projects/${enc(project)}/checks`, void 0, signal),
|
|
@@ -853,7 +1033,16 @@ var TOOL_CAPABILITY = {
|
|
|
853
1033
|
myna_list_assets: "assets:read",
|
|
854
1034
|
myna_get_asset: "assets:read",
|
|
855
1035
|
myna_upload_asset: "assets:write",
|
|
856
|
-
myna_update_asset: "assets:write"
|
|
1036
|
+
myna_update_asset: "assets:write",
|
|
1037
|
+
myna_list_reports: "feedback:read",
|
|
1038
|
+
myna_get_report: "feedback:read",
|
|
1039
|
+
myna_get_report_attachment: "feedback:read",
|
|
1040
|
+
myna_find_similar_reports: "feedback:read",
|
|
1041
|
+
myna_report_digest: "feedback:read",
|
|
1042
|
+
myna_reply_to_report: "feedback:write",
|
|
1043
|
+
myna_update_report: "feedback:write",
|
|
1044
|
+
myna_resolve_report: "feedback:write",
|
|
1045
|
+
myna_request_retest: "feedback:write"
|
|
857
1046
|
};
|
|
858
1047
|
function registerTools(server, registry, options = { allowPathUploads: true }) {
|
|
859
1048
|
server.registerTool(
|
|
@@ -1925,6 +2114,265 @@ function registerTools(server, registry, options = { allowPathUploads: true }) {
|
|
|
1925
2114
|
}
|
|
1926
2115
|
}
|
|
1927
2116
|
);
|
|
2117
|
+
if (options.products?.includes("feedback")) registerFeedbackTools(server, registry);
|
|
2118
|
+
}
|
|
2119
|
+
var reportArg = {
|
|
2120
|
+
...projectArg,
|
|
2121
|
+
report: z.string().describe("Report id (rep_...), number, or #number.")
|
|
2122
|
+
};
|
|
2123
|
+
function registerFeedbackTools(server, registry) {
|
|
2124
|
+
server.registerTool(
|
|
2125
|
+
"myna_list_reports",
|
|
2126
|
+
{
|
|
2127
|
+
title: "List reports",
|
|
2128
|
+
description: "List bug reports, QA findings, and feature requests, most recently active first. Filter by board, status, type, priority, assignee, labels, free-text search over title and body, and last-activity time. Returns compact rows; call myna_get_report for the whole picture.",
|
|
2129
|
+
inputSchema: {
|
|
2130
|
+
...projectArg,
|
|
2131
|
+
board: z.string().optional().describe("Board key, e.g. qa."),
|
|
2132
|
+
status: z.string().optional().describe("Comma-separated: triage, open, in_progress, needs_retest, resolved, closed, duplicate, wont_fix, spam."),
|
|
2133
|
+
type: z.string().optional().describe("bug, qa, feedback, or feature_request."),
|
|
2134
|
+
priority: z.string().optional().describe("low, normal, high, or urgent."),
|
|
2135
|
+
labels: z.string().optional().describe("Comma-separated labels; matches any."),
|
|
2136
|
+
q: z.string().optional().describe("Full-text search over title and body."),
|
|
2137
|
+
since: z.string().optional().describe("ISO timestamp; only reports active at or after it."),
|
|
2138
|
+
limit: z.number().optional()
|
|
2139
|
+
},
|
|
2140
|
+
annotations: { readOnlyHint: true, openWorldHint: true }
|
|
2141
|
+
},
|
|
2142
|
+
async (args) => {
|
|
2143
|
+
try {
|
|
2144
|
+
const { client, project } = registry.clientFor(args.project);
|
|
2145
|
+
const page = await client.reports.list(project, {
|
|
2146
|
+
board: args.board,
|
|
2147
|
+
status: args.status,
|
|
2148
|
+
type: args.type,
|
|
2149
|
+
priority: args.priority,
|
|
2150
|
+
labels: args.labels?.split(","),
|
|
2151
|
+
q: args.q,
|
|
2152
|
+
since: args.since,
|
|
2153
|
+
limit: args.limit
|
|
2154
|
+
});
|
|
2155
|
+
return ok(`${page.data.length} report(s).`, { reports: page.data, nextCursor: page.nextCursor });
|
|
2156
|
+
} catch (error) {
|
|
2157
|
+
return fail(error);
|
|
2158
|
+
}
|
|
2159
|
+
}
|
|
2160
|
+
);
|
|
2161
|
+
server.registerTool(
|
|
2162
|
+
"myna_get_report",
|
|
2163
|
+
{
|
|
2164
|
+
title: "Get report",
|
|
2165
|
+
description: "Everything known about one report, in a single call: the description, the board's guidance for answering it, custom fields, the environment the application supplied, the full timeline with named actors, an attachment manifest, and linked commits or pull requests. Use this before trying to reproduce or fix anything.",
|
|
2166
|
+
inputSchema: { ...reportArg },
|
|
2167
|
+
annotations: { readOnlyHint: true, openWorldHint: true }
|
|
2168
|
+
},
|
|
2169
|
+
async (args) => {
|
|
2170
|
+
try {
|
|
2171
|
+
const { client, project } = registry.clientFor(args.project);
|
|
2172
|
+
const report = await client.reports.get(project, args.report);
|
|
2173
|
+
const summary = [
|
|
2174
|
+
`#${report.number} ${report.title}`,
|
|
2175
|
+
`${report.status} / ${report.priority} on board ${report.boardKey}`,
|
|
2176
|
+
`${report.attachments.length} attachment(s), ${report.timeline.length} timeline entr(ies)`
|
|
2177
|
+
].join(" \u2014 ");
|
|
2178
|
+
return ok(summary, report);
|
|
2179
|
+
} catch (error) {
|
|
2180
|
+
return fail(error);
|
|
2181
|
+
}
|
|
2182
|
+
}
|
|
2183
|
+
);
|
|
2184
|
+
server.registerTool(
|
|
2185
|
+
"myna_get_report_attachment",
|
|
2186
|
+
{
|
|
2187
|
+
title: "Get report attachment",
|
|
2188
|
+
description: "Fetch one attachment from a report. Images come back as image content the model can actually look at; parsed logs come back structured. Attachment ids are listed by myna_get_report.",
|
|
2189
|
+
inputSchema: { ...reportArg, attachment: z.string().describe("Attachment id (rat_...).") },
|
|
2190
|
+
annotations: { readOnlyHint: true, openWorldHint: true }
|
|
2191
|
+
},
|
|
2192
|
+
async (args) => {
|
|
2193
|
+
try {
|
|
2194
|
+
const { client, project } = registry.clientFor(args.project);
|
|
2195
|
+
const attachment = await client.reports.attachment(project, args.report, args.attachment);
|
|
2196
|
+
if (attachment.kind === "image" && attachment.url) {
|
|
2197
|
+
const response = await fetch(attachment.url);
|
|
2198
|
+
if (response.ok) {
|
|
2199
|
+
const buffer = Buffer.from(await response.arrayBuffer());
|
|
2200
|
+
return {
|
|
2201
|
+
content: [
|
|
2202
|
+
{ type: "text", text: `${attachment.filename} (${attachment.byteSize} bytes)` },
|
|
2203
|
+
{ type: "image", data: buffer.toString("base64"), mimeType: attachment.contentType }
|
|
2204
|
+
]
|
|
2205
|
+
};
|
|
2206
|
+
}
|
|
2207
|
+
}
|
|
2208
|
+
if (attachment.structured) {
|
|
2209
|
+
return ok(`${attachment.filename} (parsed).`, { attachment });
|
|
2210
|
+
}
|
|
2211
|
+
return ok(
|
|
2212
|
+
`${attachment.filename} \u2014 ${attachment.kind}, ${attachment.byteSize} bytes. Download it with the url field; it is short-lived.`,
|
|
2213
|
+
{ attachment }
|
|
2214
|
+
);
|
|
2215
|
+
} catch (error) {
|
|
2216
|
+
return fail(error);
|
|
2217
|
+
}
|
|
2218
|
+
}
|
|
2219
|
+
);
|
|
2220
|
+
server.registerTool(
|
|
2221
|
+
"myna_reply_to_report",
|
|
2222
|
+
{
|
|
2223
|
+
title: "Reply to a report",
|
|
2224
|
+
description: "Post a message on a report. visibility 'public' reaches whoever filed it; 'internal' is a note for the team and is the default. Say what changed and why, not that you are an AI.",
|
|
2225
|
+
inputSchema: {
|
|
2226
|
+
...reportArg,
|
|
2227
|
+
message: z.string(),
|
|
2228
|
+
visibility: z.enum(["internal", "public"]).optional().describe("Defaults to internal. Use public to answer the reporter.")
|
|
2229
|
+
},
|
|
2230
|
+
annotations: { openWorldHint: true }
|
|
2231
|
+
},
|
|
2232
|
+
async (args) => {
|
|
2233
|
+
try {
|
|
2234
|
+
const { client, project } = registry.clientFor(args.project);
|
|
2235
|
+
const report = await client.reports.comment(project, args.report, {
|
|
2236
|
+
body: args.message,
|
|
2237
|
+
visibility: args.visibility ?? "internal"
|
|
2238
|
+
});
|
|
2239
|
+
return ok(`Posted ${args.visibility ?? "internal"} reply on #${report.number}.`, { report });
|
|
2240
|
+
} catch (error) {
|
|
2241
|
+
return fail(error);
|
|
2242
|
+
}
|
|
2243
|
+
}
|
|
2244
|
+
);
|
|
2245
|
+
server.registerTool(
|
|
2246
|
+
"myna_update_report",
|
|
2247
|
+
{
|
|
2248
|
+
title: "Triage a report",
|
|
2249
|
+
description: "Set status, priority, assignee, labels, title, or body on a report. One tool rather than five, because triage is usually several of these at once.",
|
|
2250
|
+
inputSchema: {
|
|
2251
|
+
...reportArg,
|
|
2252
|
+
status: z.string().optional(),
|
|
2253
|
+
priority: z.string().optional(),
|
|
2254
|
+
assigneeId: z.string().nullable().optional().describe("User id, or null to unassign."),
|
|
2255
|
+
labels: z.array(z.string()).optional().describe("Replaces the label set."),
|
|
2256
|
+
title: z.string().optional(),
|
|
2257
|
+
body: z.string().optional()
|
|
2258
|
+
},
|
|
2259
|
+
annotations: { openWorldHint: true }
|
|
2260
|
+
},
|
|
2261
|
+
async (args) => {
|
|
2262
|
+
try {
|
|
2263
|
+
const { client, project } = registry.clientFor(args.project);
|
|
2264
|
+
const report = await client.reports.update(project, args.report, {
|
|
2265
|
+
status: args.status,
|
|
2266
|
+
priority: args.priority,
|
|
2267
|
+
assigneeId: args.assigneeId,
|
|
2268
|
+
labels: args.labels,
|
|
2269
|
+
title: args.title,
|
|
2270
|
+
body: args.body
|
|
2271
|
+
});
|
|
2272
|
+
return ok(`#${report.number} is now ${report.status} (${report.priority}).`, { report });
|
|
2273
|
+
} catch (error) {
|
|
2274
|
+
return fail(error);
|
|
2275
|
+
}
|
|
2276
|
+
}
|
|
2277
|
+
);
|
|
2278
|
+
server.registerTool(
|
|
2279
|
+
"myna_resolve_report",
|
|
2280
|
+
{
|
|
2281
|
+
title: "Resolve a report",
|
|
2282
|
+
description: "Mark a report resolved, link the commit or pull request that fixed it, and reply to the reporter \u2014 in one call. Set requestRetest to ask them to confirm the fix instead of closing it, which is usually the better move when you could not reproduce the problem yourself.",
|
|
2283
|
+
inputSchema: {
|
|
2284
|
+
...reportArg,
|
|
2285
|
+
reply: z.string().optional().describe("Public reply explaining what changed."),
|
|
2286
|
+
commit: z.string().optional(),
|
|
2287
|
+
pullRequest: z.string().optional(),
|
|
2288
|
+
requestRetest: z.boolean().optional()
|
|
2289
|
+
},
|
|
2290
|
+
annotations: { openWorldHint: true }
|
|
2291
|
+
},
|
|
2292
|
+
async (args) => {
|
|
2293
|
+
try {
|
|
2294
|
+
const { client, project } = registry.clientFor(args.project);
|
|
2295
|
+
const report = await client.reports.resolve(project, args.report, {
|
|
2296
|
+
reply: args.reply,
|
|
2297
|
+
commit: args.commit,
|
|
2298
|
+
pullRequest: args.pullRequest,
|
|
2299
|
+
requestRetest: args.requestRetest
|
|
2300
|
+
});
|
|
2301
|
+
return ok(
|
|
2302
|
+
args.requestRetest ? `#${report.number} is awaiting the reporter's confirmation.` : `#${report.number} resolved.`,
|
|
2303
|
+
{ report }
|
|
2304
|
+
);
|
|
2305
|
+
} catch (error) {
|
|
2306
|
+
return fail(error);
|
|
2307
|
+
}
|
|
2308
|
+
}
|
|
2309
|
+
);
|
|
2310
|
+
server.registerTool(
|
|
2311
|
+
"myna_request_retest",
|
|
2312
|
+
{
|
|
2313
|
+
title: "Ask for a retest",
|
|
2314
|
+
description: "Move a report to needs_retest and ask whoever filed it to confirm the fix. This is how the loop closes back to a human; prefer it over silently closing something you could not verify.",
|
|
2315
|
+
inputSchema: { ...reportArg, note: z.string().optional().describe("What they should check.") },
|
|
2316
|
+
annotations: { openWorldHint: true }
|
|
2317
|
+
},
|
|
2318
|
+
async (args) => {
|
|
2319
|
+
try {
|
|
2320
|
+
const { client, project } = registry.clientFor(args.project);
|
|
2321
|
+
const report = await client.reports.requestRetest(project, args.report, { note: args.note });
|
|
2322
|
+
return ok(`Asked for a retest on #${report.number}.`, { report });
|
|
2323
|
+
} catch (error) {
|
|
2324
|
+
return fail(error);
|
|
2325
|
+
}
|
|
2326
|
+
}
|
|
2327
|
+
);
|
|
2328
|
+
server.registerTool(
|
|
2329
|
+
"myna_report_digest",
|
|
2330
|
+
{
|
|
2331
|
+
title: "Report digest",
|
|
2332
|
+
description: "What has been reported since a time or a release: counts by status, board and type, plus the reports themselves already ordered by priority then recency. Call this before deciding what to work on \u2014 it answers in one request what would otherwise be several pages of myna_list_reports.",
|
|
2333
|
+
inputSchema: {
|
|
2334
|
+
...projectArg,
|
|
2335
|
+
since: z.string().optional().describe("ISO timestamp. Defaults to the last 24 hours."),
|
|
2336
|
+
release: z.number().optional().describe("Since this release number shipped."),
|
|
2337
|
+
limit: z.number().optional()
|
|
2338
|
+
},
|
|
2339
|
+
annotations: { readOnlyHint: true, openWorldHint: true }
|
|
2340
|
+
},
|
|
2341
|
+
async (args) => {
|
|
2342
|
+
try {
|
|
2343
|
+
const { client, project } = registry.clientFor(args.project);
|
|
2344
|
+
const digest = await client.reports.digest(project, {
|
|
2345
|
+
since: args.since,
|
|
2346
|
+
release: args.release,
|
|
2347
|
+
limit: args.limit
|
|
2348
|
+
});
|
|
2349
|
+
return ok(
|
|
2350
|
+
`${digest.total} report(s) since ${digest.sinceLabel}; ${digest.stillOpen} still open.`,
|
|
2351
|
+
digest
|
|
2352
|
+
);
|
|
2353
|
+
} catch (error) {
|
|
2354
|
+
return fail(error);
|
|
2355
|
+
}
|
|
2356
|
+
}
|
|
2357
|
+
);
|
|
2358
|
+
server.registerTool(
|
|
2359
|
+
"myna_find_similar_reports",
|
|
2360
|
+
{
|
|
2361
|
+
title: "Find similar reports",
|
|
2362
|
+
description: "Reports that might describe the same problem, by wording and by the route or URL the application reported. A ranking aid, not a verdict \u2014 each candidate says why it surfaced. Use it before fixing something twice, and to merge duplicates with myna_update_report.",
|
|
2363
|
+
inputSchema: { ...reportArg },
|
|
2364
|
+
annotations: { readOnlyHint: true, openWorldHint: true }
|
|
2365
|
+
},
|
|
2366
|
+
async (args) => {
|
|
2367
|
+
try {
|
|
2368
|
+
const { client, project } = registry.clientFor(args.project);
|
|
2369
|
+
const result = await client.reports.similar(project, args.report);
|
|
2370
|
+
return ok(`${result.reports.length} candidate(s).`, result);
|
|
2371
|
+
} catch (error) {
|
|
2372
|
+
return fail(error);
|
|
2373
|
+
}
|
|
2374
|
+
}
|
|
2375
|
+
);
|
|
1928
2376
|
}
|
|
1929
2377
|
|
|
1930
2378
|
// src/resources.ts
|
|
@@ -1999,7 +2447,7 @@ function registerResources(server, registry) {
|
|
|
1999
2447
|
}
|
|
2000
2448
|
|
|
2001
2449
|
// src/version.ts
|
|
2002
|
-
var VERSION = true ? "0.
|
|
2450
|
+
var VERSION = true ? "0.14.0" : "0.0.0-dev";
|
|
2003
2451
|
|
|
2004
2452
|
// src/server.ts
|
|
2005
2453
|
var SERVER_NAME = "myna";
|