@myna-sh/mcp 0.14.0 → 0.16.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,6 +1,6 @@
1
1
  # @myna-sh/mcp
2
2
 
3
- Model Context Protocol server for managing [Myna](https://myna.sh) content from coding agents and MCP clients.
3
+ Model Context Protocol server for operating [Myna](https://myna.sh) content and feedback from coding agents and MCP clients.
4
4
 
5
5
  > **Connecting Claude, ChatGPT, or another remote agent?** You do not need this package. Myna hosts the same server at `https://api.myna.sh/mcp` — add it as a custom connector and authorize it with OAuth. See [connecting a remote agent](https://docs.myna.sh/mcp/hosted). This package is for local coding agents that run the server over stdio.
6
6
 
package/dist/main.js CHANGED
@@ -564,7 +564,7 @@ var ManagementClient = class {
564
564
  */
565
565
  reports = {
566
566
  list: (project, opts = {}) => {
567
- const { board, status, type, priority, assignee, labels, q, since, ...rest } = opts;
567
+ const { board, status, type, priority, assignee, labels, q, since, reporter, property, ...rest } = opts;
568
568
  return this.page(`/projects/${enc(project)}/reports`, rest, {
569
569
  board,
570
570
  status: Array.isArray(status) ? status.join(",") : status,
@@ -573,7 +573,9 @@ var ManagementClient = class {
573
573
  assignee,
574
574
  labels: labels?.join(","),
575
575
  q,
576
- since
576
+ since,
577
+ reporter,
578
+ property
577
579
  });
578
580
  },
579
581
  get: (project, report, signal) => this.get(
@@ -629,13 +631,7 @@ var ManagementClient = class {
629
631
  void 0,
630
632
  signal
631
633
  ),
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`. */
634
+ /** Move a report to another board. Ordinary triage; every board is private. */
639
635
  move: (project, report, body) => this.mutate(
640
636
  "POST",
641
637
  `/projects/${enc(project)}/reports/${enc(String(report))}/move`,
@@ -669,14 +665,58 @@ var ManagementClient = class {
669
665
  *
670
666
  * Unlike every other credential here, one of these is *meant* to be readable
671
667
  * 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.
668
+ * server: the key can only file a report and read back what the presenting
669
+ * reporter wrote, only from an origin the project registered, and only within
670
+ * its own rate and quota budget.
671
+ *
672
+ * Both `rotate` calls return the new secret and leave the old one working for
673
+ * 24 hours, so a frontend can be rebuilt and redeployed in between.
674
674
  */
675
675
  ingestKeys = {
676
676
  list: (project, signal) => this.get(`/projects/${enc(project)}/ingest-keys`, void 0, signal),
677
677
  create: (project, body) => this.mutate("POST", `/projects/${enc(project)}/ingest-keys`, body),
678
+ rotate: (project, key) => this.mutate(
679
+ "POST",
680
+ `/projects/${enc(project)}/ingest-keys/${enc(key)}/rotate`
681
+ ),
682
+ rotateIdentitySecret: (project, key) => this.mutate(
683
+ "POST",
684
+ `/projects/${enc(project)}/ingest-keys/${enc(key)}/rotate-identity-secret`
685
+ ),
678
686
  revoke: (project, key) => this.mutate("DELETE", `/projects/${enc(project)}/ingest-keys/${enc(key)}`)
679
687
  };
688
+ // --- Feedback: reporters --------------------------------------------------
689
+ /**
690
+ * The people who filed the reports.
691
+ *
692
+ * `verified` is the only field Myna stands behind: a signature the project's
693
+ * own backend produced. `properties` is what that backend said about its own
694
+ * user — true of their system, not checked here.
695
+ */
696
+ reporters = {
697
+ list: (project, opts = {}) => {
698
+ const { q, blocked, ...rest } = opts;
699
+ return this.page(`/projects/${enc(project)}/reporters`, rest, {
700
+ q,
701
+ blocked: blocked === void 0 ? void 0 : String(blocked)
702
+ });
703
+ },
704
+ /** By id (`rpr_…`), by the customer's own user id, or by email. */
705
+ get: (project, reporter, signal) => this.get(
706
+ `/projects/${enc(project)}/reporters/${enc(reporter)}`,
707
+ void 0,
708
+ signal
709
+ ),
710
+ /**
711
+ * Block or unblock. Blocking stops notifications and closes read-back; it
712
+ * never deletes what they already filed.
713
+ */
714
+ setBlocked: (project, reporter, body) => this.mutate(
715
+ "PATCH",
716
+ `/projects/${enc(project)}/reporters/${enc(reporter)}`,
717
+ body
718
+ )
719
+ };
680
720
  // --- Declared external checks ---------------------------------------------
681
721
  checks = {
682
722
  list: (project, signal) => this.get(`/projects/${enc(project)}/checks`, void 0, signal),
@@ -1039,10 +1079,15 @@ var TOOL_CAPABILITY = {
1039
1079
  myna_get_report_attachment: "feedback:read",
1040
1080
  myna_find_similar_reports: "feedback:read",
1041
1081
  myna_report_digest: "feedback:read",
1082
+ myna_list_reporters: "feedback:read",
1083
+ myna_get_reporter: "feedback:read",
1042
1084
  myna_reply_to_report: "feedback:write",
1043
1085
  myna_update_report: "feedback:write",
1044
1086
  myna_resolve_report: "feedback:write",
1045
- myna_request_retest: "feedback:write"
1087
+ myna_request_retest: "feedback:write",
1088
+ myna_merge_report: "feedback:write",
1089
+ myna_move_report: "feedback:write",
1090
+ myna_block_reporter: "feedback:manage"
1046
1091
  };
1047
1092
  function registerTools(server, registry, options = { allowPathUploads: true }) {
1048
1093
  server.registerTool(
@@ -2359,7 +2404,7 @@ function registerFeedbackTools(server, registry) {
2359
2404
  "myna_find_similar_reports",
2360
2405
  {
2361
2406
  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.",
2407
+ 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_merge_report.",
2363
2408
  inputSchema: { ...reportArg },
2364
2409
  annotations: { readOnlyHint: true, openWorldHint: true }
2365
2410
  },
@@ -2373,6 +2418,132 @@ function registerFeedbackTools(server, registry) {
2373
2418
  }
2374
2419
  }
2375
2420
  );
2421
+ server.registerTool(
2422
+ "myna_merge_report",
2423
+ {
2424
+ title: "Merge a duplicate report",
2425
+ description: "Mark a report as a duplicate of another. Nothing is deleted: the duplicate keeps its own timeline and points at the canonical report, because the second person to hit a bug usually said something the first did not. Confirm with myna_find_similar_reports first \u2014 a wrong merge buries a distinct problem.",
2426
+ inputSchema: {
2427
+ ...reportArg,
2428
+ into: z.string().describe("The canonical report: an id, a number, or #number."),
2429
+ note: z.string().optional().describe("Explains the merge on both timelines.")
2430
+ },
2431
+ annotations: { openWorldHint: true }
2432
+ },
2433
+ async (args) => {
2434
+ try {
2435
+ const { client, project } = registry.clientFor(args.project);
2436
+ const report = await client.reports.merge(project, args.report, {
2437
+ into: args.into,
2438
+ note: args.note
2439
+ });
2440
+ return ok(`#${report.number} marked as a duplicate of ${args.into}.`, { report });
2441
+ } catch (error) {
2442
+ return fail(error);
2443
+ }
2444
+ }
2445
+ );
2446
+ server.registerTool(
2447
+ "myna_move_report",
2448
+ {
2449
+ title: "Move a report to another board",
2450
+ description: "Move a report to a different board on the same project \u2014 a bug filed on the ideas board, a QA finding that belongs with the bugs. Every board is private to the project, so this changes which queue the report sits in and nothing about who can read it.",
2451
+ inputSchema: { ...reportArg, board: z.string().describe("Destination board key.") },
2452
+ annotations: { openWorldHint: true }
2453
+ },
2454
+ async (args) => {
2455
+ try {
2456
+ const { client, project } = registry.clientFor(args.project);
2457
+ const report = await client.reports.move(project, args.report, { board: args.board });
2458
+ return ok(`#${report.number} moved to ${report.boardKey}.`, { report });
2459
+ } catch (error) {
2460
+ return fail(error);
2461
+ }
2462
+ }
2463
+ );
2464
+ server.registerTool(
2465
+ "myna_list_reporters",
2466
+ {
2467
+ title: "List reporters",
2468
+ description: "The people who have filed reports on this project: how many each has filed, when they were last active, whether their identity was signed by the application's own backend, and the properties that backend attached. Use it to see whether a wave of reports is one person or twenty.",
2469
+ inputSchema: {
2470
+ ...projectArg,
2471
+ query: z.string().optional().describe("Match an email, display name, or external id."),
2472
+ blocked: z.boolean().optional().describe("Only blocked reporters."),
2473
+ limit: z.number().optional(),
2474
+ cursor: z.string().optional()
2475
+ },
2476
+ annotations: { readOnlyHint: true, openWorldHint: true }
2477
+ },
2478
+ async (args) => {
2479
+ try {
2480
+ const { client, project } = registry.clientFor(args.project);
2481
+ const page = await client.reporters.list(project, {
2482
+ q: args.query,
2483
+ blocked: args.blocked,
2484
+ limit: args.limit,
2485
+ cursor: args.cursor
2486
+ });
2487
+ return ok(`${page.data.length} reporter(s).`, {
2488
+ reporters: page.data,
2489
+ nextCursor: page.nextCursor
2490
+ });
2491
+ } catch (error) {
2492
+ return fail(error);
2493
+ }
2494
+ }
2495
+ );
2496
+ server.registerTool(
2497
+ "myna_get_reporter",
2498
+ {
2499
+ title: "Get a reporter",
2500
+ description: "One reporter and everything they have filed, in one call. Reference them by Myna id (rpr_...), by the application's own user id, or by email address. Read this before answering somebody who has reported the same area three times.",
2501
+ inputSchema: {
2502
+ ...projectArg,
2503
+ reporter: z.string().describe("Reporter id (rpr_...), your own user id, or an email address.")
2504
+ },
2505
+ annotations: { readOnlyHint: true, openWorldHint: true }
2506
+ },
2507
+ async (args) => {
2508
+ try {
2509
+ const { client, project } = registry.clientFor(args.project);
2510
+ const reporter = await client.reporters.get(project, args.reporter);
2511
+ return ok(
2512
+ `${reporter.displayName ?? reporter.email ?? reporter.id}: ${reporter.reportCount} report(s), ${reporter.verified ? "verified" : "unverified"}.`,
2513
+ reporter
2514
+ );
2515
+ } catch (error) {
2516
+ return fail(error);
2517
+ }
2518
+ }
2519
+ );
2520
+ server.registerTool(
2521
+ "myna_block_reporter",
2522
+ {
2523
+ title: "Block or unblock a reporter",
2524
+ description: "Stop a reporter filing, reading their reports back, or being emailed \u2014 or let them back in. Nothing they already filed is deleted or hidden. This is moderation, not triage: use it for abuse, not for somebody who reports too much.",
2525
+ inputSchema: {
2526
+ ...projectArg,
2527
+ reporter: z.string().describe("Reporter id (rpr_...), your own user id, or an email address."),
2528
+ blocked: z.boolean().describe("True to block, false to unblock.")
2529
+ },
2530
+ annotations: { destructiveHint: true, openWorldHint: true }
2531
+ },
2532
+ async (args) => {
2533
+ try {
2534
+ const { client, project } = registry.clientFor(args.project);
2535
+ const reporter = await client.reporters.setBlocked(project, args.reporter, {
2536
+ blocked: args.blocked
2537
+ });
2538
+ return ok(
2539
+ `${reporter.displayName ?? reporter.email ?? reporter.id} is now ${reporter.blocked ? "blocked" : "active"}.`,
2540
+ reporter
2541
+ );
2542
+ } catch (error) {
2543
+ return fail(error);
2544
+ }
2545
+ }
2546
+ );
2376
2547
  }
2377
2548
 
2378
2549
  // src/resources.ts
@@ -2447,7 +2618,7 @@ function registerResources(server, registry) {
2447
2618
  }
2448
2619
 
2449
2620
  // src/version.ts
2450
- var VERSION = true ? "0.14.0" : "0.0.0-dev";
2621
+ var VERSION = true ? "0.16.0" : "0.0.0-dev";
2451
2622
 
2452
2623
  // src/server.ts
2453
2624
  var SERVER_NAME = "myna";