@vibesharingapp/mcp-server 0.4.2 → 0.6.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.
Files changed (2) hide show
  1. package/dist/index.js +400 -21
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -59,8 +59,39 @@ class VibesharingClient {
59
59
  async getPrototype(id) {
60
60
  return this.request(`/api/prototypes/${id}`);
61
61
  }
62
- async getFeedback(projectId) {
63
- return this.request(`/api/feedback?projectId=${projectId}`);
62
+ async getFeedback(projectId, filters) {
63
+ const params = new URLSearchParams({ projectId });
64
+ if (filters?.status)
65
+ params.set("status", filters.status);
66
+ if (filters?.priority)
67
+ params.set("priority", filters.priority);
68
+ if (filters?.assigned_to)
69
+ params.set("assignedTo", filters.assigned_to);
70
+ return this.request(`/api/feedback?${params.toString()}`);
71
+ }
72
+ async triageFeedback(feedbackIds, updates) {
73
+ if (feedbackIds.length === 1) {
74
+ return this.request("/api/feedback", {
75
+ method: "PATCH",
76
+ body: JSON.stringify({
77
+ feedbackId: feedbackIds[0],
78
+ status: updates.status,
79
+ priority: updates.priority,
80
+ assignedTo: updates.assigned_to,
81
+ resolutionNote: updates.resolution_note,
82
+ }),
83
+ });
84
+ }
85
+ return this.request("/api/feedback/bulk", {
86
+ method: "PATCH",
87
+ body: JSON.stringify({
88
+ feedbackIds,
89
+ status: updates.status,
90
+ priority: updates.priority,
91
+ assignedTo: updates.assigned_to,
92
+ resolutionNote: updates.resolution_note,
93
+ }),
94
+ });
64
95
  }
65
96
  async syncContext(projectId, content) {
66
97
  return this.request("/api/context", {
@@ -133,6 +164,31 @@ class VibesharingClient {
133
164
  method: "DELETE",
134
165
  });
135
166
  }
167
+ async generateFeedbackTopics(projectId, topics) {
168
+ return this.request("/api/feedback-topics", {
169
+ method: "POST",
170
+ body: JSON.stringify({
171
+ projectId,
172
+ topics,
173
+ source: "auto",
174
+ }),
175
+ });
176
+ }
177
+ async notifyFeedbackResolved(projectId, feedbackIds, deployUrl) {
178
+ return this.request("/api/feedback/notify-resolved", {
179
+ method: "POST",
180
+ body: JSON.stringify({ projectId, feedbackIds, deployUrl }),
181
+ });
182
+ }
183
+ async updateFeedbackBrief(projectId, brief, focus) {
184
+ const updates = { feedback_brief: brief };
185
+ if (focus)
186
+ updates.feedback_focus = focus;
187
+ return this.request(`/api/prototypes/${projectId}`, {
188
+ method: "PATCH",
189
+ body: JSON.stringify(updates),
190
+ });
191
+ }
136
192
  }
137
193
  /**
138
194
  * Simple fuzzy text matching. Scores based on:
@@ -178,20 +234,31 @@ function fuzzyMatch(query, items, getName, threshold = 0.3) {
178
234
  .sort((a, b) => b.score - a.score);
179
235
  }
180
236
  // ---- Version tracking & What's New ----
181
- const CURRENT_VERSION = "0.4.2";
237
+ const CURRENT_VERSION = "0.6.0";
182
238
  const WHATS_NEW = {
183
- "0.4.0": [
184
- "🆕 VibeSharing MCP v0.4.0 — What's New:",
239
+ "0.6.0": [
240
+ "🆕 VibeSharing MCP v0.6.0 — What's New:",
241
+ "",
242
+ "• Auto-generated feedback questions — After deploying, use generate_feedback_topics",
243
+ " to create 3-5 targeted questions that guide stakeholders toward useful feedback.",
244
+ " Questions are categorized by theme: vision alignment, feasibility, design fidelity,",
245
+ " or interaction design. Previous auto-generated questions are replaced on each deploy.",
246
+ "• Feedback briefs — Include a brief parameter to set context stakeholders see in the",
247
+ " Context tab. Also auto-extracted from ## Feedback Brief in CLAUDE.md at deploy time.",
248
+ ].join("\n"),
249
+ "0.5.0": [
250
+ "🆕 VibeSharing MCP v0.5.0 — What's New:",
185
251
  "",
186
- "• resolve_target toolCall this before deploying to confirm the collection,",
187
- " project name, and deploy URL with the user. Fuzzy-matches names so you don't",
188
- " need exact IDs.",
189
- "• Named deploymentsUse deploy_name on import_repo or deploy_files to set a",
190
- " friendly Vercel URL (e.g., 'erg-v3-teams' → erg-v3-teams.vercel.app).",
191
- " Fuzzy search — list_collections and list_prototypes now accept a 'search'",
192
- " parameter to filter results.",
193
- "• GuardrailsDeploy tools now ask for confirmation when collection or project",
194
- " info is missing, instead of auto-generating everything silently.",
252
+ "• Feedback triageget_feedback now supports status, priority, and assignee",
253
+ " filters. Use triage_feedback to update status (open/in_progress/resolved/",
254
+ " wont_fix/deferred), set priority, and assign feedback to team members.",
255
+ "• New tool: triage_feedback Bulk update feedback items without leaving your editor.",
256
+ ].join("\n"),
257
+ "0.4.0": [
258
+ " resolve_target tool — Fuzzy-matches collection/project names.",
259
+ "• Named deployments Set friendly Vercel URLs with deploy_name.",
260
+ " Fuzzy search on list_collections and list_prototypes.",
261
+ "• Guardrails on deploy tools.",
195
262
  ].join("\n"),
196
263
  };
197
264
  function getWhatsNew() {
@@ -242,7 +309,7 @@ const client = new VibesharingClient(VIBESHARING_URL, VIBESHARING_TOKEN);
242
309
  // Create MCP server
243
310
  const server = new index_js_1.Server({
244
311
  name: "vibesharing",
245
- version: "0.4.0",
312
+ version: "0.6.0",
246
313
  }, {
247
314
  capabilities: {
248
315
  tools: {},
@@ -319,7 +386,7 @@ server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => {
319
386
  },
320
387
  {
321
388
  name: "get_feedback",
322
- description: "Get feedback and comments for a specific prototype. Use this to see what the team thinks about a prototype.",
389
+ description: "Get feedback and comments for a specific prototype. Can filter by status (open, in_progress, resolved, wont_fix, deferred), priority (critical, high, medium, low), or assignee.",
323
390
  inputSchema: {
324
391
  type: "object",
325
392
  properties: {
@@ -327,10 +394,96 @@ server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => {
327
394
  type: "string",
328
395
  description: "The VibeSharing project/prototype ID",
329
396
  },
397
+ status: {
398
+ type: "string",
399
+ description: "Filter by status: open, in_progress, resolved, wont_fix, deferred. Comma-separated for multiple.",
400
+ },
401
+ priority: {
402
+ type: "string",
403
+ description: "Filter by priority: critical, high, medium, low",
404
+ },
405
+ assigned_to: {
406
+ type: "string",
407
+ description: "Filter by assignee user ID, or 'unassigned' for unassigned feedback",
408
+ },
330
409
  },
331
410
  required: ["project_id"],
332
411
  },
333
412
  },
413
+ {
414
+ name: "triage_feedback",
415
+ description: "Update status, priority, or assignee on one or more feedback items. Use this to triage feedback from within your editor.",
416
+ inputSchema: {
417
+ type: "object",
418
+ properties: {
419
+ feedback_ids: {
420
+ type: "array",
421
+ items: { type: "string" },
422
+ description: "One or more feedback IDs to update",
423
+ },
424
+ status: {
425
+ type: "string",
426
+ enum: ["open", "in_progress", "resolved", "wont_fix", "deferred"],
427
+ description: "New status for the feedback items",
428
+ },
429
+ priority: {
430
+ type: "string",
431
+ enum: ["critical", "high", "medium", "low"],
432
+ description: "New priority for the feedback items. Omit to leave unchanged.",
433
+ },
434
+ assigned_to: {
435
+ type: "string",
436
+ description: "User ID to assign to. Use empty string to unassign.",
437
+ },
438
+ resolution_note: {
439
+ type: "string",
440
+ description: "Brief explanation of how the feedback was addressed (shown to the stakeholder). Only meaningful when status is resolved/wont_fix/deferred.",
441
+ },
442
+ },
443
+ required: ["feedback_ids"],
444
+ },
445
+ },
446
+ {
447
+ name: "close_feedback_loop",
448
+ description: "CALL THIS AFTER DEPLOYING when there is open feedback. Matches what you just built to open feedback items, resolves them with explanations, and notifies the original stakeholders with a personalized digest. The stakeholder sees exactly what happened to their feedback.\n\nFlow:\n1. Pull open feedback via get_feedback\n2. Look at what you built and match changes to feedback items\n3. Call this tool with the resolutions\n4. Stakeholders get email: 'Your feedback was addressed' with per-item explanations",
449
+ inputSchema: {
450
+ type: "object",
451
+ properties: {
452
+ project_id: {
453
+ type: "string",
454
+ description: "The VibeSharing prototype ID",
455
+ },
456
+ resolutions: {
457
+ type: "array",
458
+ items: {
459
+ type: "object",
460
+ properties: {
461
+ feedback_id: {
462
+ type: "string",
463
+ description: "The feedback item ID being resolved",
464
+ },
465
+ status: {
466
+ type: "string",
467
+ enum: ["resolved", "wont_fix", "deferred", "in_progress"],
468
+ description: "What happened: resolved (fixed), wont_fix (intentional), deferred (later), in_progress (started but not done)",
469
+ },
470
+ note: {
471
+ type: "string",
472
+ description: "Brief explanation of what changed or why it was deferred. This is shown directly to the stakeholder. Be specific: 'Nav restructured to separate admin and user flows' not 'Fixed the navigation'.",
473
+ },
474
+ },
475
+ required: ["feedback_id", "status", "note"],
476
+ },
477
+ description: "Array of feedback items to resolve with explanations",
478
+ },
479
+ deploy_url: {
480
+ type: "string",
481
+ description: "Optional: URL of the new deploy (included in notification so stakeholders can see the update)",
482
+ },
483
+ },
484
+ required: ["project_id", "resolutions"],
485
+ },
486
+ },
334
487
  {
335
488
  name: "sync_context",
336
489
  description: "Sync your CLAUDE.md, AGENTS.md, or project context to VibeSharing. This helps maintain context across AI sessions and team members.",
@@ -583,6 +736,52 @@ server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => {
583
736
  required: ["link_id"],
584
737
  },
585
738
  },
739
+ {
740
+ name: "generate_feedback_topics",
741
+ description: "Auto-generate feedback questions for a prototype based on what was built. IMPORTANT: Before generating questions, ask the user: 'What type of feedback is most important for this deploy?' and present these options:\n\n 1. Awareness only — just sharing progress, no feedback needed\n 2. Design direction — brand, visual, layout feedback\n 3. Technical feasibility — is this buildable, are these features doable\n 4. Vision alignment — does this match where we're going\n 5. Interaction design — usability, flow, UX patterns\n 6. Full review — all feedback welcome (default)\n\nUse their answer as the 'focus' parameter. If 'awareness', skip topic generation and just set the brief. Otherwise generate 3-5 questions, weighting toward the chosen focus theme.",
742
+ inputSchema: {
743
+ type: "object",
744
+ properties: {
745
+ project_id: {
746
+ type: "string",
747
+ description: "The VibeSharing prototype ID",
748
+ },
749
+ focus: {
750
+ type: "string",
751
+ enum: ["awareness", "design", "feasibility", "vision", "interaction", "full"],
752
+ description: "The type of feedback the designer wants. 'awareness' = no questions, just FYI. Others emphasize that theme. 'full' = all themes equally. Default: 'full'.",
753
+ },
754
+ brief: {
755
+ type: "string",
756
+ description: "A short (2-4 sentence) feedback brief explaining what this prototype is and what's ready for review. Stored on the prototype and shown to stakeholders in the Context tab.",
757
+ },
758
+ topics: {
759
+ type: "array",
760
+ items: {
761
+ type: "object",
762
+ properties: {
763
+ title: {
764
+ type: "string",
765
+ description: "The feedback question (e.g., 'Does this navigation flow match how your team actually works?')",
766
+ },
767
+ description: {
768
+ type: "string",
769
+ description: "Optional: Additional context for the question",
770
+ },
771
+ theme: {
772
+ type: "string",
773
+ enum: ["vision", "feasibility", "design", "interaction"],
774
+ description: "Question theme: 'vision' (alignment with goals), 'feasibility' (technical possibility), 'design' (brand/visual fidelity), 'interaction' (usability/UX patterns)",
775
+ },
776
+ },
777
+ required: ["title"],
778
+ },
779
+ description: "Array of feedback questions to create. Generate 3-5 based on what you built. Weight toward the focus theme (e.g., if focus is 'feasibility', 2-3 questions should be feasibility-themed). Not required when focus is 'awareness'.",
780
+ },
781
+ },
782
+ required: ["project_id"],
783
+ },
784
+ },
586
785
  ],
587
786
  };
588
787
  });
@@ -716,26 +915,35 @@ server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
716
915
  };
717
916
  }
718
917
  case "get_feedback": {
719
- const { project_id } = args;
720
- const result = await client.getFeedback(project_id);
918
+ const { project_id, status: statusFilter, priority: priorityFilter, assigned_to: assignedToFilter } = args;
919
+ const result = await client.getFeedback(project_id, {
920
+ status: statusFilter,
921
+ priority: priorityFilter,
922
+ assigned_to: assignedToFilter,
923
+ });
721
924
  const feedback = result.feedback || [];
722
925
  if (feedback.length === 0) {
926
+ const filterNote = statusFilter || priorityFilter || assignedToFilter
927
+ ? " matching your filters"
928
+ : "";
723
929
  return {
724
930
  content: [
725
931
  {
726
932
  type: "text",
727
- text: "No feedback yet for this prototype. Share it with your team to get their thoughts!",
933
+ text: `No feedback${filterNote} for this prototype. Share it with your team to get their thoughts!`,
728
934
  },
729
935
  ],
730
936
  };
731
937
  }
732
938
  const feedbackList = feedback
733
939
  .map((f) => {
734
- const status = f.resolved_at ? " [Resolved]" : "";
940
+ const itemStatus = f.status || (f.resolved_at ? "resolved" : "open");
941
+ const priorityLabel = f.priority ? ` [${f.priority.toUpperCase()}]` : "";
942
+ const assigneeLabel = f.assignee_name ? ` → ${f.assignee_name}` : "";
735
943
  const replies = f.replies && f.replies.length > 0
736
944
  ? `\n Replies: ${f.replies.length}`
737
945
  : "";
738
- return `- ${f.user_name}${status}: "${f.content}"\n ${new Date(f.created_at).toLocaleDateString()}${replies}`;
946
+ return `- [${itemStatus}]${priorityLabel}${assigneeLabel} ${f.user_name}: "${f.content}"\n ID: ${f.id}\n ${new Date(f.created_at).toLocaleDateString()}${replies}`;
739
947
  })
740
948
  .join("\n\n");
741
949
  return {
@@ -747,6 +955,98 @@ server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
747
955
  ],
748
956
  };
749
957
  }
958
+ case "triage_feedback": {
959
+ const { feedback_ids, status: newStatus, priority: newPriority, assigned_to: newAssignee, resolution_note } = args;
960
+ if (!feedback_ids || feedback_ids.length === 0) {
961
+ return {
962
+ content: [{ type: "text", text: "Error: feedback_ids array is required" }],
963
+ };
964
+ }
965
+ const updates = {};
966
+ if (newStatus)
967
+ updates.status = newStatus;
968
+ if (newPriority !== undefined)
969
+ updates.priority = newPriority;
970
+ if (newAssignee !== undefined)
971
+ updates.assigned_to = newAssignee || null;
972
+ if (resolution_note)
973
+ updates.resolution_note = resolution_note;
974
+ const result = await client.triageFeedback(feedback_ids, updates);
975
+ const changes = [];
976
+ if (newStatus)
977
+ changes.push(`status → ${newStatus}`);
978
+ if (newPriority)
979
+ changes.push(`priority → ${newPriority}`);
980
+ if (newAssignee !== undefined)
981
+ changes.push(newAssignee ? `assigned → ${newAssignee}` : "unassigned");
982
+ if (resolution_note)
983
+ changes.push(`note: "${resolution_note}"`);
984
+ return {
985
+ content: [
986
+ {
987
+ type: "text",
988
+ text: `Updated ${feedback_ids.length} feedback item(s): ${changes.join(", ")}`,
989
+ },
990
+ ],
991
+ };
992
+ }
993
+ case "close_feedback_loop": {
994
+ const { project_id, resolutions, deploy_url } = args;
995
+ if (!resolutions || resolutions.length === 0) {
996
+ return {
997
+ content: [{ type: "text", text: "Error: At least one resolution is required." }],
998
+ };
999
+ }
1000
+ // Step 1: Resolve each feedback item with its note
1001
+ const resolvedIds = [];
1002
+ const errors = [];
1003
+ for (const res of resolutions) {
1004
+ try {
1005
+ await client.triageFeedback([res.feedback_id], {
1006
+ status: res.status,
1007
+ resolution_note: res.note,
1008
+ });
1009
+ resolvedIds.push(res.feedback_id);
1010
+ }
1011
+ catch (err) {
1012
+ errors.push(`Failed to update ${res.feedback_id}: ${err}`);
1013
+ }
1014
+ }
1015
+ // Step 2: Notify stakeholders
1016
+ let notifyResult = { notified: 0, emailed: 0, stakeholders: 0 };
1017
+ if (resolvedIds.length > 0) {
1018
+ try {
1019
+ notifyResult = await client.notifyFeedbackResolved(project_id, resolvedIds, deploy_url);
1020
+ }
1021
+ catch (err) {
1022
+ errors.push(`Notification failed: ${err}`);
1023
+ }
1024
+ }
1025
+ const statusLabels = {
1026
+ resolved: "Resolved",
1027
+ wont_fix: "Won't fix",
1028
+ deferred: "Deferred",
1029
+ in_progress: "In progress",
1030
+ };
1031
+ const lines = [
1032
+ `✅ Feedback loop closed — ${resolvedIds.length} item${resolvedIds.length === 1 ? "" : "s"} updated:`,
1033
+ "",
1034
+ ...resolutions.map((r, i) => {
1035
+ const label = statusLabels[r.status] || r.status;
1036
+ const success = resolvedIds.includes(r.feedback_id) ? "✓" : "✗";
1037
+ return ` ${success} [${label}] ${r.note}`;
1038
+ }),
1039
+ ];
1040
+ if (notifyResult.stakeholders > 0) {
1041
+ lines.push("", `📬 ${notifyResult.stakeholders} stakeholder${notifyResult.stakeholders === 1 ? "" : "s"} notified (${notifyResult.emailed} email${notifyResult.emailed === 1 ? "" : "s"} sent)`, "Each stakeholder received a personalized digest showing what happened to their specific feedback.");
1042
+ }
1043
+ if (errors.length > 0) {
1044
+ lines.push("", `⚠️ ${errors.length} error(s):`, ...errors.map(e => ` - ${e}`));
1045
+ }
1046
+ return {
1047
+ content: [{ type: "text", text: lines.join("\n") }],
1048
+ };
1049
+ }
750
1050
  case "sync_context": {
751
1051
  const { project_id, content } = args;
752
1052
  await client.syncContext(project_id, content);
@@ -1173,6 +1473,85 @@ server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
1173
1473
  ],
1174
1474
  };
1175
1475
  }
1476
+ case "generate_feedback_topics": {
1477
+ const { project_id, topics, brief, focus } = args;
1478
+ const feedbackFocus = focus || "full";
1479
+ const themeLabels = {
1480
+ vision: "Vision Alignment",
1481
+ feasibility: "Feasibility",
1482
+ design: "Design Fidelity",
1483
+ interaction: "Interaction Design",
1484
+ };
1485
+ const focusLabels = {
1486
+ awareness: "Awareness only",
1487
+ design: "Design direction",
1488
+ feasibility: "Technical feasibility",
1489
+ vision: "Vision alignment",
1490
+ interaction: "Interaction design",
1491
+ full: "Full review",
1492
+ };
1493
+ // Store brief and focus
1494
+ try {
1495
+ await client.updateFeedbackBrief(project_id, brief || "", feedbackFocus);
1496
+ }
1497
+ catch (err) {
1498
+ console.error("Failed to store feedback brief/focus:", err);
1499
+ }
1500
+ // Awareness mode: no questions, just the brief
1501
+ if (feedbackFocus === "awareness") {
1502
+ // Clear any existing auto-generated questions
1503
+ try {
1504
+ await client.generateFeedbackTopics(project_id, []);
1505
+ }
1506
+ catch {
1507
+ // OK if this fails — just means no old auto topics to clear
1508
+ }
1509
+ const lines = [
1510
+ `📢 Prototype set to awareness mode — no feedback questions will be shown.`,
1511
+ ];
1512
+ if (brief) {
1513
+ lines.push("", `📋 Brief saved: "${brief.substring(0, 100)}${brief.length > 100 ? "..." : ""}"`);
1514
+ }
1515
+ lines.push("", "Stakeholders will see this as an FYI with the Context tab available.", "They can still leave general comments if they choose to.");
1516
+ return {
1517
+ content: [{ type: "text", text: lines.join("\n") }],
1518
+ };
1519
+ }
1520
+ // All other modes: generate questions
1521
+ if (!topics || topics.length === 0) {
1522
+ return {
1523
+ content: [{ type: "text", text: "Error: At least one topic is required when focus is not 'awareness'." }],
1524
+ };
1525
+ }
1526
+ // Sort topics: focused theme first, then others
1527
+ const sortedTopics = feedbackFocus !== "full"
1528
+ ? [
1529
+ ...topics.filter(t => t.theme === feedbackFocus),
1530
+ ...topics.filter(t => t.theme !== feedbackFocus),
1531
+ ]
1532
+ : topics;
1533
+ const result = await client.generateFeedbackTopics(project_id, sortedTopics);
1534
+ const created = result.topics || [];
1535
+ const lines = [
1536
+ `✅ Created ${created.length} feedback question${created.length === 1 ? "" : "s"} (focus: ${focusLabels[feedbackFocus] || feedbackFocus}):`,
1537
+ "",
1538
+ ...created.map((t, i) => {
1539
+ const tag = t.theme ? ` [${themeLabels[t.theme] || t.theme}]` : "";
1540
+ const star = t.theme === feedbackFocus ? " ★" : "";
1541
+ return ` ${i + 1}. ${t.title}${tag}${star}`;
1542
+ }),
1543
+ ];
1544
+ if (brief) {
1545
+ lines.push("", `📋 Feedback brief saved — stakeholders can see it in the Context tab.`);
1546
+ }
1547
+ if (feedbackFocus !== "full") {
1548
+ lines.push("", `${themeLabels[feedbackFocus] || feedbackFocus} questions are pinned to the top for stakeholders.`);
1549
+ }
1550
+ lines.push("", "Previous auto-generated questions were replaced. Manual questions are untouched.");
1551
+ return {
1552
+ content: [{ type: "text", text: lines.join("\n") }],
1553
+ };
1554
+ }
1176
1555
  default:
1177
1556
  return {
1178
1557
  content: [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vibesharingapp/mcp-server",
3
- "version": "0.4.2",
3
+ "version": "0.6.0",
4
4
  "description": "MCP server for VibeSharing - register prototypes and get feedback directly from Claude Code",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",