super-feedback-mcp 0.1.1 → 0.2.1

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 (4) hide show
  1. package/README.md +31 -19
  2. package/dist/index.js +158 -84
  3. package/package.json +1 -1
  4. package/src/index.ts +233 -110
package/README.md CHANGED
@@ -64,36 +64,47 @@ Or if published to npm:
64
64
 
65
65
  ## Available Tools
66
66
 
67
- ### 1. `get_open_feedback`
67
+ ### 1. `get_feedback_summary`
68
68
 
69
- Retrieves all open (unresolved) feedback comments for your project.
69
+ Gets a lightweight summary of all feedback for the project.
70
70
 
71
71
  **Parameters:**
72
- - `includeResolved` (boolean, optional): Include resolved comments too
73
72
  - `pageFilter` (string, optional): Filter to a specific page path (e.g., `/pricing`)
74
73
 
75
74
  **Returns:**
76
75
  - Project name
77
- - Total count of matching feedback
78
- - Array of feedback items with:
79
- - Feedback text
80
- - Element context (tag, text, test IDs, CSS classes)
81
- - Page URL and extracted route path
82
- - Hints for locating code (possible file paths, search terms)
76
+ - Total count, open count, resolved count
77
+ - Brief list of comments (ID, feedback preview, status, page path)
83
78
 
84
- ### 2. `mark_feedback_resolved`
79
+ Use this first to understand what feedback exists.
85
80
 
86
- Marks a feedback comment as resolved after you've fixed the issue.
81
+ ### 2. `get_comment_details`
82
+
83
+ Gets complete details for a specific feedback comment.
87
84
 
88
85
  **Parameters:**
89
- - `commentId` (string): The comment ID to mark as resolved
86
+ - `commentId` (string): The comment ID to get full details for
90
87
 
91
- ### 3. `get_feedback_details`
88
+ **Returns:**
89
+ - Full feedback text and author info
90
+ - Complete element data:
91
+ - `tagName`, `elementText`, `nearbyText`
92
+ - `section`, `sectionSelector`
93
+ - `selectors` (cssPath, nthPath, testId, id)
94
+ - `xpath`, `boundingBox`
95
+ - `parentChain`, `dataAttributes`, `computedStyles`
96
+ - Page URL and file path hints
97
+ - Search terms for code location
98
+ - All replies/thread history
99
+ - Design suggestions (if any)
100
+ - Cross-element references (if any)
101
+
102
+ ### 3. `mark_feedback_resolved`
92
103
 
93
- Gets full details for a specific feedback comment.
104
+ Marks a feedback comment as resolved after you've fixed the issue.
94
105
 
95
106
  **Parameters:**
96
- - `commentId` (string): The comment ID to get details for
107
+ - `commentId` (string): The comment ID to mark as resolved
97
108
 
98
109
  ## Usage Example
99
110
 
@@ -102,10 +113,11 @@ In Cursor, you can ask the AI agent:
102
113
  > "Get all open feedback for my project and fix them"
103
114
 
104
115
  The agent will:
105
- 1. Call `get_open_feedback` to retrieve all open comments
106
- 2. For each comment, use the hints to search your codebase
107
- 3. Make the necessary code changes
108
- 4. Call `mark_feedback_resolved` to update the status
116
+ 1. Call `get_feedback_summary` to see what feedback exists
117
+ 2. Call `get_comment_details` for each open comment to get full context
118
+ 3. Use the selectors and hints to search your codebase
119
+ 4. Make the necessary code changes
120
+ 5. Call `mark_feedback_resolved` to update the status
109
121
 
110
122
  ## How It Works
111
123
 
package/dist/index.js CHANGED
@@ -11,6 +11,15 @@ if (!ACCESS_CODE && !ADMIN_CODE) {
11
11
  console.error("Error: Either SUPER_FEEDBACK_ACCESS_CODE or SUPER_FEEDBACK_ADMIN_CODE environment variable is required");
12
12
  process.exit(1);
13
13
  }
14
+ // Helper to get auth params
15
+ function getAuthParams() {
16
+ const params = new URLSearchParams();
17
+ if (ACCESS_CODE)
18
+ params.set("accessCode", ACCESS_CODE);
19
+ if (ADMIN_CODE)
20
+ params.set("adminCode", ADMIN_CODE);
21
+ return params;
22
+ }
14
23
  // API call helper
15
24
  async function callConvexAPI(endpoint, options) {
16
25
  const url = `${CONVEX_URL}${endpoint}`;
@@ -30,54 +39,72 @@ async function callConvexAPI(endpoint, options) {
30
39
  // Create MCP server
31
40
  const server = new McpServer({
32
41
  name: "super-feedback",
33
- version: "0.1.0",
42
+ version: "0.2.1",
34
43
  });
35
- // Tool 1: Get open feedback
36
- server.registerTool("get_open_feedback", {
37
- title: "Get Open Feedback",
38
- description: `Retrieves all open (unresolved) feedback comments for the configured Super Feedback project.
39
-
40
- Returns feedback formatted for AI consumption with:
41
- - The actual feedback/comment text
42
- - Element context (tag, text content, CSS classes, test IDs)
43
- - Page URL and likely file paths
44
- - Search hints to help locate the code
44
+ // Tool 1: Get feedback summary (lightweight overview)
45
+ server.registerTool("get_feedback_summary", {
46
+ title: "Get Feedback Summary",
47
+ description: `Gets a lightweight summary of all feedback for the project.
48
+
49
+ Returns:
50
+ - Total comment count
51
+ - Open (unresolved) count
52
+ - Resolved count
53
+ - List of comment IDs with brief info (feedback text, status, page path)
45
54
 
46
- Use this to understand what changes clients have requested.`,
55
+ Use this first to understand what feedback exists, then use get_comment_details for full context on specific comments.`,
47
56
  inputSchema: {
48
- includeResolved: z.boolean().optional().describe("Include resolved comments too (default: false)"),
49
57
  pageFilter: z.string().optional().describe("Filter to specific page path (e.g., '/pricing')"),
50
58
  },
51
59
  outputSchema: {
52
60
  projectName: z.string(),
53
61
  totalCount: z.number(),
54
- comments: z.array(z.any()), // Flexible schema to allow all API fields
62
+ openCount: z.number(),
63
+ resolvedCount: z.number(),
64
+ comments: z.array(z.object({
65
+ id: z.string(),
66
+ index: z.number(),
67
+ feedback: z.string(),
68
+ status: z.string(),
69
+ page: z.string(),
70
+ })),
55
71
  },
56
- }, async ({ includeResolved = false, pageFilter }) => {
72
+ }, async ({ pageFilter }) => {
57
73
  try {
58
- // Build query params
59
- const params = new URLSearchParams();
60
- if (ACCESS_CODE)
61
- params.set("accessCode", ACCESS_CODE);
62
- if (ADMIN_CODE)
63
- params.set("adminCode", ADMIN_CODE);
64
- if (!includeResolved)
65
- params.set("status", "open");
74
+ const params = getAuthParams();
75
+ params.set("status", "all"); // Get all to count properly
66
76
  if (pageFilter)
67
77
  params.set("page", pageFilter);
68
- // Use the AI-optimized endpoint
69
- const response = await callConvexAPI(`/api/feedback/ai?${params.toString()}`);
78
+ const response = await callConvexAPI(`/api/feedback/ai/summary?${params.toString()}`);
70
79
  const output = {
71
80
  projectName: response.project?.name || "Unknown Project",
72
81
  totalCount: response.totalCount,
73
- comments: response.comments,
82
+ openCount: response.openCount,
83
+ resolvedCount: response.resolvedCount,
84
+ comments: response.comments.map(c => ({
85
+ id: c.id,
86
+ index: c.index,
87
+ feedback: c.feedback.slice(0, 100) + (c.feedback.length > 100 ? "..." : ""),
88
+ status: c.status,
89
+ page: c.page.path,
90
+ })),
74
91
  };
75
- // Create human-readable summary
76
- const summary = response.comments.map(c => `#${c.index} [${c.status}] "${c.feedback}" on <${c.element?.tag || "element"}> "${c.element?.text || ""}" (${c.page.path})`).join("\n");
92
+ // Create human-readable summary with actual comment IDs
93
+ const openComments = response.comments.filter(c => c.status === "open");
94
+ const summary = openComments.length > 0
95
+ ? openComments.map(c => `• [${c.id}] "${c.feedback.slice(0, 60)}${c.feedback.length > 60 ? "..." : ""}" (${c.page.path})`).join("\n")
96
+ : "No open feedback.";
77
97
  return {
78
98
  content: [{
79
99
  type: "text",
80
- text: `Found ${response.totalCount} feedback item(s):\n\n${summary}\n\nFull data available in structuredContent.`,
100
+ text: `šŸ“Š Feedback Summary for "${response.project?.name || "Project"}"
101
+
102
+ Total: ${response.totalCount} | Open: ${response.openCount} | Resolved: ${response.resolvedCount}
103
+
104
+ ${response.openCount > 0 ? `Open feedback (${openComments.length}):\n${summary}` : "All feedback resolved! šŸŽ‰"}
105
+
106
+ To get full details for a comment, use: get_comment_details(commentId)
107
+ Example: get_comment_details("${openComments[0]?.id || "comment-id-here"}")`,
81
108
  }],
82
109
  structuredContent: output,
83
110
  };
@@ -85,18 +112,116 @@ Use this to understand what changes clients have requested.`,
85
112
  catch (error) {
86
113
  const message = error instanceof Error ? error.message : "Unknown error";
87
114
  return {
88
- content: [{ type: "text", text: `Error fetching feedback: ${message}` }],
115
+ content: [{ type: "text", text: `Error fetching feedback summary: ${message}` }],
89
116
  isError: true,
90
117
  };
91
118
  }
92
119
  });
93
- // Tool 2: Mark feedback as resolved
120
+ // Tool 2: Get full details for a single comment
121
+ server.registerTool("get_comment_details", {
122
+ title: "Get Comment Details",
123
+ description: `Gets complete details for a specific feedback comment.
124
+
125
+ Returns everything needed to work on the feedback:
126
+ - Full feedback text and author info
127
+ - Complete element data (tagName, text, selectors, xpath, boundingBox)
128
+ - CSS selectors (cssPath, nthPath, testId, id)
129
+ - Section context and nearby text
130
+ - Page URL and file path hints
131
+ - All replies/thread history
132
+ - Design suggestions (if any)
133
+ - Cross-element references (if any)
134
+
135
+ Use this after get_feedback_summary to dive into a specific comment.`,
136
+ inputSchema: {
137
+ commentId: z.string().describe("The comment ID to get full details for"),
138
+ },
139
+ outputSchema: {
140
+ found: z.boolean(),
141
+ comment: z.any().nullable(),
142
+ },
143
+ }, async ({ commentId }) => {
144
+ try {
145
+ const params = getAuthParams();
146
+ params.set("commentId", commentId);
147
+ const response = await callConvexAPI(`/api/feedback/ai/comment?${params.toString()}`);
148
+ if (!response.found || !response.comment) {
149
+ return {
150
+ content: [{ type: "text", text: `Comment ${commentId} not found` }],
151
+ structuredContent: { found: false, comment: null },
152
+ };
153
+ }
154
+ const c = response.comment;
155
+ const el = c.element;
156
+ // Build comprehensive text summary
157
+ let text = `šŸ“ Feedback #${commentId}
158
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
159
+
160
+ šŸ’¬ Feedback: "${c.feedback}"
161
+ šŸ‘¤ Author: ${c.author}${c.authorEmail ? ` (${c.authorEmail})` : ""}
162
+ šŸ“… Created: ${c.createdAt}
163
+ šŸ”– Status: ${c.status}
164
+ šŸ”— Page: ${c.page.path}`;
165
+ if (el) {
166
+ text += `
167
+
168
+ šŸŽÆ Element Details:
169
+ ā”œā”€ Tag: <${el.tagName}>
170
+ ā”œā”€ Text: "${el.elementText}"
171
+ ā”œā”€ Section: ${el.section}${el.sectionSelector ? ` (${el.sectionSelector})` : ""}
172
+ ā”œā”€ Nearby: "${el.nearbyText.slice(0, 100)}..."
173
+ │
174
+ ā”œā”€ Selectors:
175
+ │ ā”œā”€ CSS Path: ${el.selectors.cssPath}
176
+ │ ā”œā”€ Nth Path: ${el.selectors.nthPath}
177
+ │ ā”œā”€ XPath: ${el.xpath}
178
+ │ ${el.selectors.testId ? `ā”œā”€ Test ID: ${el.selectors.testId}` : ""}
179
+ │ ${el.selectors.id ? `ā”œā”€ Element ID: ${el.selectors.id}` : ""}
180
+ │
181
+ └─ Bounding Box: x:${el.boundingBox.x} y:${el.boundingBox.y} w:${el.boundingBox.width} h:${el.boundingBox.height}`;
182
+ if (el.dataAttributes && Object.keys(el.dataAttributes).length > 0) {
183
+ text += `\n\nšŸ“¦ Data Attributes: ${JSON.stringify(el.dataAttributes)}`;
184
+ }
185
+ }
186
+ if (c.hints.searchTerms.length > 0) {
187
+ text += `\n\nšŸ” Search Terms: ${c.hints.searchTerms.join(", ")}`;
188
+ }
189
+ if (c.hints.possibleFiles.length > 0) {
190
+ text += `\nšŸ“ Possible Files: ${c.hints.possibleFiles.join(", ")}`;
191
+ }
192
+ if (c.replies && c.replies.length > 0) {
193
+ text += `\n\nšŸ’¬ Thread (${c.replies.length} replies):`;
194
+ c.replies.forEach((r, i) => {
195
+ text += `\n${i + 1}. ${r.authorName}: "${r.content}" (${r.createdAt})`;
196
+ });
197
+ }
198
+ if (c.designSuggestion) {
199
+ text += `\n\nšŸŽØ Design Suggestion:`;
200
+ c.designSuggestion.changes.forEach(change => {
201
+ text += `\n ${change.property}: ${change.oldValue} → ${change.newValue}`;
202
+ });
203
+ }
204
+ return {
205
+ content: [{ type: "text", text }],
206
+ structuredContent: { found: true, comment: c },
207
+ };
208
+ }
209
+ catch (error) {
210
+ const message = error instanceof Error ? error.message : "Unknown error";
211
+ return {
212
+ content: [{ type: "text", text: `Error fetching comment details: ${message}` }],
213
+ structuredContent: { found: false, comment: null },
214
+ isError: true,
215
+ };
216
+ }
217
+ });
218
+ // Tool 3: Mark feedback as resolved
94
219
  server.registerTool("mark_feedback_resolved", {
95
220
  title: "Mark Feedback Resolved",
96
221
  description: `Marks a feedback comment as resolved after you've fixed the issue.
97
222
 
98
223
  Call this after making the code changes to update the feedback status.
99
- The comment ID can be obtained from get_open_feedback.
224
+ The comment ID can be obtained from get_feedback_summary.
100
225
 
101
226
  Note: Requires SUPER_FEEDBACK_ADMIN_CODE to be configured.`,
102
227
  inputSchema: {
@@ -125,7 +250,7 @@ Note: Requires SUPER_FEEDBACK_ADMIN_CODE to be configured.`,
125
250
  });
126
251
  const output = {
127
252
  success: true,
128
- message: `Comment ${commentId} marked as resolved`,
253
+ message: `āœ… Comment ${commentId} marked as resolved`,
129
254
  };
130
255
  return {
131
256
  content: [{ type: "text", text: output.message }],
@@ -141,57 +266,6 @@ Note: Requires SUPER_FEEDBACK_ADMIN_CODE to be configured.`,
141
266
  };
142
267
  }
143
268
  });
144
- // Tool 3: Get feedback details
145
- server.registerTool("get_feedback_details", {
146
- title: "Get Feedback Details",
147
- description: `Gets full details for a specific feedback comment, including all element data and metadata.
148
-
149
- Use this when you need more context about a specific piece of feedback.`,
150
- inputSchema: {
151
- commentId: z.string().describe("The comment ID to get details for"),
152
- },
153
- outputSchema: {
154
- found: z.boolean(),
155
- comment: z.any().nullable(),
156
- },
157
- }, async ({ commentId }) => {
158
- try {
159
- // Build query params - get all comments and find the specific one
160
- const params = new URLSearchParams();
161
- if (ACCESS_CODE)
162
- params.set("accessCode", ACCESS_CODE);
163
- if (ADMIN_CODE)
164
- params.set("adminCode", ADMIN_CODE);
165
- params.set("status", "all"); // Get all statuses
166
- const response = await callConvexAPI(`/api/feedback/ai?${params.toString()}`);
167
- const comment = response.comments?.find(c => c.id === commentId);
168
- if (!comment) {
169
- return {
170
- content: [{ type: "text", text: `Comment ${commentId} not found` }],
171
- structuredContent: { found: false, comment: null },
172
- };
173
- }
174
- const output = {
175
- found: true,
176
- comment: comment,
177
- };
178
- return {
179
- content: [{
180
- type: "text",
181
- text: `Feedback: "${comment.feedback}"\nElement: <${comment.element?.tag || "unknown"}> "${comment.element?.text || ""}"\nPage: ${comment.page.path}\nSearch terms: ${comment.hints.searchTerms.join(", ")}`,
182
- }],
183
- structuredContent: output,
184
- };
185
- }
186
- catch (error) {
187
- const message = error instanceof Error ? error.message : "Unknown error";
188
- return {
189
- content: [{ type: "text", text: `Error: ${message}` }],
190
- structuredContent: { found: false, comment: null },
191
- isError: true,
192
- };
193
- }
194
- });
195
269
  // Start the server
196
270
  async function main() {
197
271
  const transport = new StdioServerTransport();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "super-feedback-mcp",
3
- "version": "0.1.1",
3
+ "version": "0.2.1",
4
4
  "description": "MCP server for Super Feedback - enables AI agents to query and resolve client feedback",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
package/src/index.ts CHANGED
@@ -4,33 +4,74 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
4
4
  import * as z from "zod";
5
5
 
6
6
  // Types for API responses
7
- interface FeedbackComment {
7
+ interface CommentSummary {
8
8
  id: string;
9
9
  index: number;
10
10
  feedback: string;
11
11
  author: string;
12
12
  status: string;
13
13
  createdAt: string;
14
- element: {
15
- tag?: string;
16
- text?: string;
17
- testId?: string;
18
- id?: string;
19
- cssPath?: string;
20
- section?: string;
21
- nearbyText?: string;
22
- selector?: string;
23
- } | null;
24
- page: { url: string; path: string };
25
- hints: { possibleFiles: string[]; searchTerms: string[] };
26
- raw?: { elementSelector?: string; elementXPath?: string };
14
+ page: { path: string };
27
15
  }
28
16
 
29
- interface FeedbackResponse {
17
+ interface FeedbackSummaryResponse {
30
18
  project: { id: string; name: string; url: string };
31
- isAdmin?: boolean;
32
19
  totalCount: number;
33
- comments: FeedbackComment[];
20
+ openCount: number;
21
+ resolvedCount: number;
22
+ comments: CommentSummary[];
23
+ }
24
+
25
+ interface Reply {
26
+ id: string;
27
+ content: string;
28
+ authorName: string;
29
+ createdAt: string;
30
+ }
31
+
32
+ interface FullCommentDetails {
33
+ id: string;
34
+ feedback: string;
35
+ author: string;
36
+ authorEmail?: string;
37
+ status: string;
38
+ createdAt: string;
39
+ editedAt?: string;
40
+ page: { url: string; path: string };
41
+ element: {
42
+ tagName: string;
43
+ elementText: string;
44
+ nearbyText: string;
45
+ section: string;
46
+ sectionSelector?: string;
47
+ selectors: {
48
+ id?: string;
49
+ testId?: string;
50
+ cssPath: string;
51
+ nthPath: string;
52
+ };
53
+ xpath: string;
54
+ boundingBox: { x: number; y: number; width: number; height: number };
55
+ parentChain?: Array<{ tag: string; id?: string; classes: string[] }>;
56
+ dataAttributes?: Record<string, string>;
57
+ computedStyles?: {
58
+ fontSize?: string;
59
+ fontWeight?: string;
60
+ color?: string;
61
+ backgroundColor?: string;
62
+ };
63
+ } | null;
64
+ hints: { possibleFiles: string[]; searchTerms: string[] };
65
+ replies: Reply[];
66
+ designSuggestion?: {
67
+ changes: Array<{ property: string; oldValue: string; newValue: string }>;
68
+ elementSelector: string;
69
+ };
70
+ references?: Array<{
71
+ selector: string;
72
+ elementText: string;
73
+ label?: string;
74
+ }>;
34
75
  }
35
76
 
36
77
  // Configuration from environment
@@ -44,6 +85,14 @@ if (!ACCESS_CODE && !ADMIN_CODE) {
44
85
  process.exit(1);
45
86
  }
46
87
 
88
+ // Helper to get auth params
89
+ function getAuthParams(): URLSearchParams {
90
+ const params = new URLSearchParams();
91
+ if (ACCESS_CODE) params.set("accessCode", ACCESS_CODE);
92
+ if (ADMIN_CODE) params.set("adminCode", ADMIN_CODE);
93
+ return params;
94
+ }
95
+
47
96
  // API call helper
48
97
  async function callConvexAPI<T>(endpoint: string, options?: RequestInit): Promise<T> {
49
98
  const url = `${CONVEX_URL}${endpoint}`;
@@ -66,76 +115,210 @@ async function callConvexAPI<T>(endpoint: string, options?: RequestInit): Promis
66
115
  // Create MCP server
67
116
  const server = new McpServer({
68
117
  name: "super-feedback",
69
- version: "0.1.0",
118
+ version: "0.2.1",
70
119
  });
71
120
 
72
- // Tool 1: Get open feedback
121
+ // Tool 1: Get feedback summary (lightweight overview)
73
122
  server.registerTool(
74
- "get_open_feedback",
123
+ "get_feedback_summary",
75
124
  {
76
- title: "Get Open Feedback",
77
- description: `Retrieves all open (unresolved) feedback comments for the configured Super Feedback project.
78
-
79
- Returns feedback formatted for AI consumption with:
80
- - The actual feedback/comment text
81
- - Element context (tag, text content, CSS classes, test IDs)
82
- - Page URL and likely file paths
83
- - Search hints to help locate the code
125
+ title: "Get Feedback Summary",
126
+ description: `Gets a lightweight summary of all feedback for the project.
127
+
128
+ Returns:
129
+ - Total comment count
130
+ - Open (unresolved) count
131
+ - Resolved count
132
+ - List of comment IDs with brief info (feedback text, status, page path)
84
133
 
85
- Use this to understand what changes clients have requested.`,
134
+ Use this first to understand what feedback exists, then use get_comment_details for full context on specific comments.`,
86
135
  inputSchema: {
87
- includeResolved: z.boolean().optional().describe("Include resolved comments too (default: false)"),
88
136
  pageFilter: z.string().optional().describe("Filter to specific page path (e.g., '/pricing')"),
89
137
  },
90
138
  outputSchema: {
91
139
  projectName: z.string(),
92
140
  totalCount: z.number(),
93
- comments: z.array(z.any()), // Flexible schema to allow all API fields
141
+ openCount: z.number(),
142
+ resolvedCount: z.number(),
143
+ comments: z.array(z.object({
144
+ id: z.string(),
145
+ index: z.number(),
146
+ feedback: z.string(),
147
+ status: z.string(),
148
+ page: z.string(),
149
+ })),
94
150
  },
95
151
  },
96
- async ({ includeResolved = false, pageFilter }) => {
152
+ async ({ pageFilter }) => {
97
153
  try {
98
- // Build query params
99
- const params = new URLSearchParams();
100
- if (ACCESS_CODE) params.set("accessCode", ACCESS_CODE);
101
- if (ADMIN_CODE) params.set("adminCode", ADMIN_CODE);
102
- if (!includeResolved) params.set("status", "open");
154
+ const params = getAuthParams();
155
+ params.set("status", "all"); // Get all to count properly
103
156
  if (pageFilter) params.set("page", pageFilter);
104
157
 
105
- // Use the AI-optimized endpoint
106
- const response = await callConvexAPI<FeedbackResponse>(
107
- `/api/feedback/ai?${params.toString()}`
158
+ const response = await callConvexAPI<FeedbackSummaryResponse>(
159
+ `/api/feedback/ai/summary?${params.toString()}`
108
160
  );
109
161
 
110
162
  const output = {
111
163
  projectName: response.project?.name || "Unknown Project",
112
164
  totalCount: response.totalCount,
113
- comments: response.comments,
165
+ openCount: response.openCount,
166
+ resolvedCount: response.resolvedCount,
167
+ comments: response.comments.map(c => ({
168
+ id: c.id,
169
+ index: c.index,
170
+ feedback: c.feedback.slice(0, 100) + (c.feedback.length > 100 ? "..." : ""),
171
+ status: c.status,
172
+ page: c.page.path,
173
+ })),
114
174
  };
115
175
 
116
- // Create human-readable summary
117
- const summary = response.comments.map(c =>
118
- `#${c.index} [${c.status}] "${c.feedback}" on <${c.element?.tag || "element"}> "${c.element?.text || ""}" (${c.page.path})`
119
- ).join("\n");
176
+ // Create human-readable summary with actual comment IDs
177
+ const openComments = response.comments.filter(c => c.status === "open");
178
+ const summary = openComments.length > 0
179
+ ? openComments.map(c => `• [${c.id}] "${c.feedback.slice(0, 60)}${c.feedback.length > 60 ? "..." : ""}" (${c.page.path})`).join("\n")
180
+ : "No open feedback.";
120
181
 
121
182
  return {
122
183
  content: [{
123
184
  type: "text",
124
- text: `Found ${response.totalCount} feedback item(s):\n\n${summary}\n\nFull data available in structuredContent.`,
185
+ text: `šŸ“Š Feedback Summary for "${response.project?.name || "Project"}"
186
+
187
+ Total: ${response.totalCount} | Open: ${response.openCount} | Resolved: ${response.resolvedCount}
188
+
189
+ ${response.openCount > 0 ? `Open feedback (${openComments.length}):\n${summary}` : "All feedback resolved! šŸŽ‰"}
190
+
191
+ To get full details for a comment, use: get_comment_details(commentId)
192
+ Example: get_comment_details("${openComments[0]?.id || "comment-id-here"}")`,
125
193
  }],
126
194
  structuredContent: output,
127
195
  };
128
196
  } catch (error) {
129
197
  const message = error instanceof Error ? error.message : "Unknown error";
130
198
  return {
131
- content: [{ type: "text", text: `Error fetching feedback: ${message}` }],
199
+ content: [{ type: "text", text: `Error fetching feedback summary: ${message}` }],
132
200
  isError: true,
133
201
  };
134
202
  }
135
203
  }
136
204
  );
137
205
 
138
- // Tool 2: Mark feedback as resolved
206
+ // Tool 2: Get full details for a single comment
207
+ server.registerTool(
208
+ "get_comment_details",
209
+ {
210
+ title: "Get Comment Details",
211
+ description: `Gets complete details for a specific feedback comment.
212
+
213
+ Returns everything needed to work on the feedback:
214
+ - Full feedback text and author info
215
+ - Complete element data (tagName, text, selectors, xpath, boundingBox)
216
+ - CSS selectors (cssPath, nthPath, testId, id)
217
+ - Section context and nearby text
218
+ - Page URL and file path hints
219
+ - All replies/thread history
220
+ - Design suggestions (if any)
221
+ - Cross-element references (if any)
222
+
223
+ Use this after get_feedback_summary to dive into a specific comment.`,
224
+ inputSchema: {
225
+ commentId: z.string().describe("The comment ID to get full details for"),
226
+ },
227
+ outputSchema: {
228
+ found: z.boolean(),
229
+ comment: z.any().nullable(),
230
+ },
231
+ },
232
+ async ({ commentId }) => {
233
+ try {
234
+ const params = getAuthParams();
235
+ params.set("commentId", commentId);
236
+
237
+ const response = await callConvexAPI<{ found: boolean; comment: FullCommentDetails | null }>(
238
+ `/api/feedback/ai/comment?${params.toString()}`
239
+ );
240
+
241
+ if (!response.found || !response.comment) {
242
+ return {
243
+ content: [{ type: "text", text: `Comment ${commentId} not found` }],
244
+ structuredContent: { found: false, comment: null },
245
+ };
246
+ }
247
+
248
+ const c = response.comment;
249
+ const el = c.element;
250
+
251
+ // Build comprehensive text summary
252
+ let text = `šŸ“ Feedback #${commentId}
253
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
254
+
255
+ šŸ’¬ Feedback: "${c.feedback}"
256
+ šŸ‘¤ Author: ${c.author}${c.authorEmail ? ` (${c.authorEmail})` : ""}
257
+ šŸ“… Created: ${c.createdAt}
258
+ šŸ”– Status: ${c.status}
259
+ šŸ”— Page: ${c.page.path}`;
260
+
261
+ if (el) {
262
+ text += `
263
+
264
+ šŸŽÆ Element Details:
265
+ ā”œā”€ Tag: <${el.tagName}>
266
+ ā”œā”€ Text: "${el.elementText}"
267
+ ā”œā”€ Section: ${el.section}${el.sectionSelector ? ` (${el.sectionSelector})` : ""}
268
+ ā”œā”€ Nearby: "${el.nearbyText.slice(0, 100)}..."
269
+ │
270
+ ā”œā”€ Selectors:
271
+ │ ā”œā”€ CSS Path: ${el.selectors.cssPath}
272
+ │ ā”œā”€ Nth Path: ${el.selectors.nthPath}
273
+ │ ā”œā”€ XPath: ${el.xpath}
274
+ │ ${el.selectors.testId ? `ā”œā”€ Test ID: ${el.selectors.testId}` : ""}
275
+ │ ${el.selectors.id ? `ā”œā”€ Element ID: ${el.selectors.id}` : ""}
276
+ │
277
+ └─ Bounding Box: x:${el.boundingBox.x} y:${el.boundingBox.y} w:${el.boundingBox.width} h:${el.boundingBox.height}`;
278
+
279
+ if (el.dataAttributes && Object.keys(el.dataAttributes).length > 0) {
280
+ text += `\n\nšŸ“¦ Data Attributes: ${JSON.stringify(el.dataAttributes)}`;
281
+ }
282
+ }
283
+
284
+ if (c.hints.searchTerms.length > 0) {
285
+ text += `\n\nšŸ” Search Terms: ${c.hints.searchTerms.join(", ")}`;
286
+ }
287
+
288
+ if (c.hints.possibleFiles.length > 0) {
289
+ text += `\nšŸ“ Possible Files: ${c.hints.possibleFiles.join(", ")}`;
290
+ }
291
+
292
+ if (c.replies && c.replies.length > 0) {
293
+ text += `\n\nšŸ’¬ Thread (${c.replies.length} replies):`;
294
+ c.replies.forEach((r, i) => {
295
+ text += `\n${i + 1}. ${r.authorName}: "${r.content}" (${r.createdAt})`;
296
+ });
297
+ }
298
+
299
+ if (c.designSuggestion) {
300
+ text += `\n\nšŸŽØ Design Suggestion:`;
301
+ c.designSuggestion.changes.forEach(change => {
302
+ text += `\n ${change.property}: ${change.oldValue} → ${change.newValue}`;
303
+ });
304
+ }
305
+
306
+ return {
307
+ content: [{ type: "text", text }],
308
+ structuredContent: { found: true, comment: c },
309
+ };
310
+ } catch (error) {
311
+ const message = error instanceof Error ? error.message : "Unknown error";
312
+ return {
313
+ content: [{ type: "text", text: `Error fetching comment details: ${message}` }],
314
+ structuredContent: { found: false, comment: null },
315
+ isError: true,
316
+ };
317
+ }
318
+ }
319
+ );
320
+
321
+ // Tool 3: Mark feedback as resolved
139
322
  server.registerTool(
140
323
  "mark_feedback_resolved",
141
324
  {
@@ -143,7 +326,7 @@ server.registerTool(
143
326
  description: `Marks a feedback comment as resolved after you've fixed the issue.
144
327
 
145
328
  Call this after making the code changes to update the feedback status.
146
- The comment ID can be obtained from get_open_feedback.
329
+ The comment ID can be obtained from get_feedback_summary.
147
330
 
148
331
  Note: Requires SUPER_FEEDBACK_ADMIN_CODE to be configured.`,
149
332
  inputSchema: {
@@ -175,7 +358,7 @@ Note: Requires SUPER_FEEDBACK_ADMIN_CODE to be configured.`,
175
358
 
176
359
  const output = {
177
360
  success: true,
178
- message: `Comment ${commentId} marked as resolved`,
361
+ message: `āœ… Comment ${commentId} marked as resolved`,
179
362
  };
180
363
 
181
364
  return {
@@ -193,66 +376,6 @@ Note: Requires SUPER_FEEDBACK_ADMIN_CODE to be configured.`,
193
376
  }
194
377
  );
195
378
 
196
- // Tool 3: Get feedback details
197
- server.registerTool(
198
- "get_feedback_details",
199
- {
200
- title: "Get Feedback Details",
201
- description: `Gets full details for a specific feedback comment, including all element data and metadata.
202
-
203
- Use this when you need more context about a specific piece of feedback.`,
204
- inputSchema: {
205
- commentId: z.string().describe("The comment ID to get details for"),
206
- },
207
- outputSchema: {
208
- found: z.boolean(),
209
- comment: z.any().nullable(),
210
- },
211
- },
212
- async ({ commentId }) => {
213
- try {
214
- // Build query params - get all comments and find the specific one
215
- const params = new URLSearchParams();
216
- if (ACCESS_CODE) params.set("accessCode", ACCESS_CODE);
217
- if (ADMIN_CODE) params.set("adminCode", ADMIN_CODE);
218
- params.set("status", "all"); // Get all statuses
219
-
220
- const response = await callConvexAPI<FeedbackResponse>(
221
- `/api/feedback/ai?${params.toString()}`
222
- );
223
-
224
- const comment = response.comments?.find(c => c.id === commentId);
225
-
226
- if (!comment) {
227
- return {
228
- content: [{ type: "text", text: `Comment ${commentId} not found` }],
229
- structuredContent: { found: false, comment: null },
230
- };
231
- }
232
-
233
- const output = {
234
- found: true,
235
- comment: comment,
236
- };
237
-
238
- return {
239
- content: [{
240
- type: "text",
241
- text: `Feedback: "${comment.feedback}"\nElement: <${comment.element?.tag || "unknown"}> "${comment.element?.text || ""}"\nPage: ${comment.page.path}\nSearch terms: ${comment.hints.searchTerms.join(", ")}`,
242
- }],
243
- structuredContent: output,
244
- };
245
- } catch (error) {
246
- const message = error instanceof Error ? error.message : "Unknown error";
247
- return {
248
- content: [{ type: "text", text: `Error: ${message}` }],
249
- structuredContent: { found: false, comment: null },
250
- isError: true,
251
- };
252
- }
253
- }
254
- );
255
-
256
379
  // Start the server
257
380
  async function main() {
258
381
  const transport = new StdioServerTransport();