@myna-sh/mcp 0.13.0 → 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 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
@@ -536,6 +536,147 @@ var ManagementClient = class {
536
536
  body
537
537
  )
538
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
+ };
539
680
  // --- Declared external checks ---------------------------------------------
540
681
  checks = {
541
682
  list: (project, signal) => this.get(`/projects/${enc(project)}/checks`, void 0, signal),
@@ -892,7 +1033,16 @@ var TOOL_CAPABILITY = {
892
1033
  myna_list_assets: "assets:read",
893
1034
  myna_get_asset: "assets:read",
894
1035
  myna_upload_asset: "assets:write",
895
- 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"
896
1046
  };
897
1047
  function registerTools(server, registry, options = { allowPathUploads: true }) {
898
1048
  server.registerTool(
@@ -1964,6 +2114,265 @@ function registerTools(server, registry, options = { allowPathUploads: true }) {
1964
2114
  }
1965
2115
  }
1966
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
+ );
1967
2376
  }
1968
2377
 
1969
2378
  // src/resources.ts
@@ -2038,7 +2447,7 @@ function registerResources(server, registry) {
2038
2447
  }
2039
2448
 
2040
2449
  // src/version.ts
2041
- var VERSION = true ? "0.13.0" : "0.0.0-dev";
2450
+ var VERSION = true ? "0.14.0" : "0.0.0-dev";
2042
2451
 
2043
2452
  // src/server.ts
2044
2453
  var SERVER_NAME = "myna";