super-feedback-mcp 0.1.0 → 0.2.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 (4) hide show
  1. package/README.md +31 -19
  2. package/dist/index.js +150 -93
  3. package/package.json +1 -1
  4. package/src/index.ts +225 -119
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,70 +39,71 @@ 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.0",
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(),
62
+ openCount: z.number(),
63
+ resolvedCount: z.number(),
54
64
  comments: z.array(z.object({
55
65
  id: z.string(),
56
66
  index: z.number(),
57
67
  feedback: z.string(),
58
- author: z.string(),
59
68
  status: z.string(),
60
- createdAt: z.string(),
61
- element: z.any().nullable(),
62
- page: z.object({
63
- url: z.string(),
64
- path: z.string(),
65
- }),
66
- hints: z.object({
67
- possibleFiles: z.array(z.string()),
68
- searchTerms: z.array(z.string()),
69
- }),
69
+ page: z.string(),
70
70
  })),
71
71
  },
72
- }, async ({ includeResolved = false, pageFilter }) => {
72
+ }, async ({ pageFilter }) => {
73
73
  try {
74
- // Build query params
75
- const params = new URLSearchParams();
76
- if (ACCESS_CODE)
77
- params.set("accessCode", ACCESS_CODE);
78
- if (ADMIN_CODE)
79
- params.set("adminCode", ADMIN_CODE);
80
- if (!includeResolved)
81
- params.set("status", "open");
74
+ const params = getAuthParams();
75
+ params.set("status", "all"); // Get all to count properly
82
76
  if (pageFilter)
83
77
  params.set("page", pageFilter);
84
- // Use the AI-optimized endpoint
85
- const response = await callConvexAPI(`/api/feedback/ai?${params.toString()}`);
78
+ const response = await callConvexAPI(`/api/feedback/ai/summary?${params.toString()}`);
86
79
  const output = {
87
80
  projectName: response.project?.name || "Unknown Project",
88
81
  totalCount: response.totalCount,
89
- 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
+ })),
90
91
  };
91
92
  // Create human-readable summary
92
- 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");
93
+ const openComments = response.comments.filter(c => c.status === "open");
94
+ const summary = openComments.length > 0
95
+ ? openComments.map(c => `• #${c.index} [${c.status}] "${c.feedback.slice(0, 50)}..." (${c.page.path})`).join("\n")
96
+ : "No open feedback.";
93
97
  return {
94
98
  content: [{
95
99
  type: "text",
96
- 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:\n${summary}` : "All feedback resolved! šŸŽ‰"}
105
+
106
+ Use get_comment_details(commentId) to get full context for a specific comment.`,
97
107
  }],
98
108
  structuredContent: output,
99
109
  };
@@ -101,18 +111,116 @@ Use this to understand what changes clients have requested.`,
101
111
  catch (error) {
102
112
  const message = error instanceof Error ? error.message : "Unknown error";
103
113
  return {
104
- content: [{ type: "text", text: `Error fetching feedback: ${message}` }],
114
+ content: [{ type: "text", text: `Error fetching feedback summary: ${message}` }],
105
115
  isError: true,
106
116
  };
107
117
  }
108
118
  });
109
- // Tool 2: Mark feedback as resolved
119
+ // Tool 2: Get full details for a single comment
120
+ server.registerTool("get_comment_details", {
121
+ title: "Get Comment Details",
122
+ description: `Gets complete details for a specific feedback comment.
123
+
124
+ Returns everything needed to work on the feedback:
125
+ - Full feedback text and author info
126
+ - Complete element data (tagName, text, selectors, xpath, boundingBox)
127
+ - CSS selectors (cssPath, nthPath, testId, id)
128
+ - Section context and nearby text
129
+ - Page URL and file path hints
130
+ - All replies/thread history
131
+ - Design suggestions (if any)
132
+ - Cross-element references (if any)
133
+
134
+ Use this after get_feedback_summary to dive into a specific comment.`,
135
+ inputSchema: {
136
+ commentId: z.string().describe("The comment ID to get full details for"),
137
+ },
138
+ outputSchema: {
139
+ found: z.boolean(),
140
+ comment: z.any().nullable(),
141
+ },
142
+ }, async ({ commentId }) => {
143
+ try {
144
+ const params = getAuthParams();
145
+ params.set("commentId", commentId);
146
+ const response = await callConvexAPI(`/api/feedback/ai/comment?${params.toString()}`);
147
+ if (!response.found || !response.comment) {
148
+ return {
149
+ content: [{ type: "text", text: `Comment ${commentId} not found` }],
150
+ structuredContent: { found: false, comment: null },
151
+ };
152
+ }
153
+ const c = response.comment;
154
+ const el = c.element;
155
+ // Build comprehensive text summary
156
+ let text = `šŸ“ Feedback #${commentId}
157
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
158
+
159
+ šŸ’¬ Feedback: "${c.feedback}"
160
+ šŸ‘¤ Author: ${c.author}${c.authorEmail ? ` (${c.authorEmail})` : ""}
161
+ šŸ“… Created: ${c.createdAt}
162
+ šŸ”– Status: ${c.status}
163
+ šŸ”— Page: ${c.page.path}`;
164
+ if (el) {
165
+ text += `
166
+
167
+ šŸŽÆ Element Details:
168
+ ā”œā”€ Tag: <${el.tagName}>
169
+ ā”œā”€ Text: "${el.elementText}"
170
+ ā”œā”€ Section: ${el.section}${el.sectionSelector ? ` (${el.sectionSelector})` : ""}
171
+ ā”œā”€ Nearby: "${el.nearbyText.slice(0, 100)}..."
172
+ │
173
+ ā”œā”€ Selectors:
174
+ │ ā”œā”€ CSS Path: ${el.selectors.cssPath}
175
+ │ ā”œā”€ Nth Path: ${el.selectors.nthPath}
176
+ │ ā”œā”€ XPath: ${el.xpath}
177
+ │ ${el.selectors.testId ? `ā”œā”€ Test ID: ${el.selectors.testId}` : ""}
178
+ │ ${el.selectors.id ? `ā”œā”€ Element ID: ${el.selectors.id}` : ""}
179
+ │
180
+ └─ Bounding Box: x:${el.boundingBox.x} y:${el.boundingBox.y} w:${el.boundingBox.width} h:${el.boundingBox.height}`;
181
+ if (el.dataAttributes && Object.keys(el.dataAttributes).length > 0) {
182
+ text += `\n\nšŸ“¦ Data Attributes: ${JSON.stringify(el.dataAttributes)}`;
183
+ }
184
+ }
185
+ if (c.hints.searchTerms.length > 0) {
186
+ text += `\n\nšŸ” Search Terms: ${c.hints.searchTerms.join(", ")}`;
187
+ }
188
+ if (c.hints.possibleFiles.length > 0) {
189
+ text += `\nšŸ“ Possible Files: ${c.hints.possibleFiles.join(", ")}`;
190
+ }
191
+ if (c.replies && c.replies.length > 0) {
192
+ text += `\n\nšŸ’¬ Thread (${c.replies.length} replies):`;
193
+ c.replies.forEach((r, i) => {
194
+ text += `\n${i + 1}. ${r.authorName}: "${r.content}" (${r.createdAt})`;
195
+ });
196
+ }
197
+ if (c.designSuggestion) {
198
+ text += `\n\nšŸŽØ Design Suggestion:`;
199
+ c.designSuggestion.changes.forEach(change => {
200
+ text += `\n ${change.property}: ${change.oldValue} → ${change.newValue}`;
201
+ });
202
+ }
203
+ return {
204
+ content: [{ type: "text", text }],
205
+ structuredContent: { found: true, comment: c },
206
+ };
207
+ }
208
+ catch (error) {
209
+ const message = error instanceof Error ? error.message : "Unknown error";
210
+ return {
211
+ content: [{ type: "text", text: `Error fetching comment details: ${message}` }],
212
+ structuredContent: { found: false, comment: null },
213
+ isError: true,
214
+ };
215
+ }
216
+ });
217
+ // Tool 3: Mark feedback as resolved
110
218
  server.registerTool("mark_feedback_resolved", {
111
219
  title: "Mark Feedback Resolved",
112
220
  description: `Marks a feedback comment as resolved after you've fixed the issue.
113
221
 
114
222
  Call this after making the code changes to update the feedback status.
115
- The comment ID can be obtained from get_open_feedback.
223
+ The comment ID can be obtained from get_feedback_summary.
116
224
 
117
225
  Note: Requires SUPER_FEEDBACK_ADMIN_CODE to be configured.`,
118
226
  inputSchema: {
@@ -141,7 +249,7 @@ Note: Requires SUPER_FEEDBACK_ADMIN_CODE to be configured.`,
141
249
  });
142
250
  const output = {
143
251
  success: true,
144
- message: `Comment ${commentId} marked as resolved`,
252
+ message: `āœ… Comment ${commentId} marked as resolved`,
145
253
  };
146
254
  return {
147
255
  content: [{ type: "text", text: output.message }],
@@ -157,57 +265,6 @@ Note: Requires SUPER_FEEDBACK_ADMIN_CODE to be configured.`,
157
265
  };
158
266
  }
159
267
  });
160
- // Tool 3: Get feedback details
161
- server.registerTool("get_feedback_details", {
162
- title: "Get Feedback Details",
163
- description: `Gets full details for a specific feedback comment, including all element data and metadata.
164
-
165
- Use this when you need more context about a specific piece of feedback.`,
166
- inputSchema: {
167
- commentId: z.string().describe("The comment ID to get details for"),
168
- },
169
- outputSchema: {
170
- found: z.boolean(),
171
- comment: z.any().nullable(),
172
- },
173
- }, async ({ commentId }) => {
174
- try {
175
- // Build query params - get all comments and find the specific one
176
- const params = new URLSearchParams();
177
- if (ACCESS_CODE)
178
- params.set("accessCode", ACCESS_CODE);
179
- if (ADMIN_CODE)
180
- params.set("adminCode", ADMIN_CODE);
181
- params.set("status", "all"); // Get all statuses
182
- const response = await callConvexAPI(`/api/feedback/ai?${params.toString()}`);
183
- const comment = response.comments?.find(c => c.id === commentId);
184
- if (!comment) {
185
- return {
186
- content: [{ type: "text", text: `Comment ${commentId} not found` }],
187
- structuredContent: { found: false, comment: null },
188
- };
189
- }
190
- const output = {
191
- found: true,
192
- comment: comment,
193
- };
194
- return {
195
- content: [{
196
- type: "text",
197
- text: `Feedback: "${comment.feedback}"\nElement: <${comment.element?.tag || "unknown"}> "${comment.element?.text || ""}"\nPage: ${comment.page.path}\nSearch terms: ${comment.hints.searchTerms.join(", ")}`,
198
- }],
199
- structuredContent: output,
200
- };
201
- }
202
- catch (error) {
203
- const message = error instanceof Error ? error.message : "Unknown error";
204
- return {
205
- content: [{ type: "text", text: `Error: ${message}` }],
206
- structuredContent: { found: false, comment: null },
207
- isError: true,
208
- };
209
- }
210
- });
211
268
  // Start the server
212
269
  async function main() {
213
270
  const transport = new StdioServerTransport();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "super-feedback-mcp",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
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,92 +115,209 @@ 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.0",
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(),
141
+ openCount: z.number(),
142
+ resolvedCount: z.number(),
93
143
  comments: z.array(z.object({
94
144
  id: z.string(),
95
145
  index: z.number(),
96
146
  feedback: z.string(),
97
- author: z.string(),
98
147
  status: z.string(),
99
- createdAt: z.string(),
100
- element: z.any().nullable(),
101
- page: z.object({
102
- url: z.string(),
103
- path: z.string(),
104
- }),
105
- hints: z.object({
106
- possibleFiles: z.array(z.string()),
107
- searchTerms: z.array(z.string()),
108
- }),
148
+ page: z.string(),
109
149
  })),
110
150
  },
111
151
  },
112
- async ({ includeResolved = false, pageFilter }) => {
152
+ async ({ pageFilter }) => {
113
153
  try {
114
- // Build query params
115
- const params = new URLSearchParams();
116
- if (ACCESS_CODE) params.set("accessCode", ACCESS_CODE);
117
- if (ADMIN_CODE) params.set("adminCode", ADMIN_CODE);
118
- if (!includeResolved) params.set("status", "open");
154
+ const params = getAuthParams();
155
+ params.set("status", "all"); // Get all to count properly
119
156
  if (pageFilter) params.set("page", pageFilter);
120
157
 
121
- // Use the AI-optimized endpoint
122
- const response = await callConvexAPI<FeedbackResponse>(
123
- `/api/feedback/ai?${params.toString()}`
158
+ const response = await callConvexAPI<FeedbackSummaryResponse>(
159
+ `/api/feedback/ai/summary?${params.toString()}`
124
160
  );
125
161
 
126
162
  const output = {
127
163
  projectName: response.project?.name || "Unknown Project",
128
164
  totalCount: response.totalCount,
129
- 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
+ })),
130
174
  };
131
175
 
132
176
  // Create human-readable summary
133
- const summary = response.comments.map(c =>
134
- `#${c.index} [${c.status}] "${c.feedback}" on <${c.element?.tag || "element"}> "${c.element?.text || ""}" (${c.page.path})`
135
- ).join("\n");
177
+ const openComments = response.comments.filter(c => c.status === "open");
178
+ const summary = openComments.length > 0
179
+ ? openComments.map(c => `• #${c.index} [${c.status}] "${c.feedback.slice(0, 50)}..." (${c.page.path})`).join("\n")
180
+ : "No open feedback.";
136
181
 
137
182
  return {
138
183
  content: [{
139
184
  type: "text",
140
- 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:\n${summary}` : "All feedback resolved! šŸŽ‰"}
190
+
191
+ Use get_comment_details(commentId) to get full context for a specific comment.`,
141
192
  }],
142
193
  structuredContent: output,
143
194
  };
144
195
  } catch (error) {
145
196
  const message = error instanceof Error ? error.message : "Unknown error";
146
197
  return {
147
- content: [{ type: "text", text: `Error fetching feedback: ${message}` }],
198
+ content: [{ type: "text", text: `Error fetching feedback summary: ${message}` }],
148
199
  isError: true,
149
200
  };
150
201
  }
151
202
  }
152
203
  );
153
204
 
154
- // Tool 2: Mark feedback as resolved
205
+ // Tool 2: Get full details for a single comment
206
+ server.registerTool(
207
+ "get_comment_details",
208
+ {
209
+ title: "Get Comment Details",
210
+ description: `Gets complete details for a specific feedback comment.
211
+
212
+ Returns everything needed to work on the feedback:
213
+ - Full feedback text and author info
214
+ - Complete element data (tagName, text, selectors, xpath, boundingBox)
215
+ - CSS selectors (cssPath, nthPath, testId, id)
216
+ - Section context and nearby text
217
+ - Page URL and file path hints
218
+ - All replies/thread history
219
+ - Design suggestions (if any)
220
+ - Cross-element references (if any)
221
+
222
+ Use this after get_feedback_summary to dive into a specific comment.`,
223
+ inputSchema: {
224
+ commentId: z.string().describe("The comment ID to get full details for"),
225
+ },
226
+ outputSchema: {
227
+ found: z.boolean(),
228
+ comment: z.any().nullable(),
229
+ },
230
+ },
231
+ async ({ commentId }) => {
232
+ try {
233
+ const params = getAuthParams();
234
+ params.set("commentId", commentId);
235
+
236
+ const response = await callConvexAPI<{ found: boolean; comment: FullCommentDetails | null }>(
237
+ `/api/feedback/ai/comment?${params.toString()}`
238
+ );
239
+
240
+ if (!response.found || !response.comment) {
241
+ return {
242
+ content: [{ type: "text", text: `Comment ${commentId} not found` }],
243
+ structuredContent: { found: false, comment: null },
244
+ };
245
+ }
246
+
247
+ const c = response.comment;
248
+ const el = c.element;
249
+
250
+ // Build comprehensive text summary
251
+ let text = `šŸ“ Feedback #${commentId}
252
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
253
+
254
+ šŸ’¬ Feedback: "${c.feedback}"
255
+ šŸ‘¤ Author: ${c.author}${c.authorEmail ? ` (${c.authorEmail})` : ""}
256
+ šŸ“… Created: ${c.createdAt}
257
+ šŸ”– Status: ${c.status}
258
+ šŸ”— Page: ${c.page.path}`;
259
+
260
+ if (el) {
261
+ text += `
262
+
263
+ šŸŽÆ Element Details:
264
+ ā”œā”€ Tag: <${el.tagName}>
265
+ ā”œā”€ Text: "${el.elementText}"
266
+ ā”œā”€ Section: ${el.section}${el.sectionSelector ? ` (${el.sectionSelector})` : ""}
267
+ ā”œā”€ Nearby: "${el.nearbyText.slice(0, 100)}..."
268
+ │
269
+ ā”œā”€ Selectors:
270
+ │ ā”œā”€ CSS Path: ${el.selectors.cssPath}
271
+ │ ā”œā”€ Nth Path: ${el.selectors.nthPath}
272
+ │ ā”œā”€ XPath: ${el.xpath}
273
+ │ ${el.selectors.testId ? `ā”œā”€ Test ID: ${el.selectors.testId}` : ""}
274
+ │ ${el.selectors.id ? `ā”œā”€ Element ID: ${el.selectors.id}` : ""}
275
+ │
276
+ └─ Bounding Box: x:${el.boundingBox.x} y:${el.boundingBox.y} w:${el.boundingBox.width} h:${el.boundingBox.height}`;
277
+
278
+ if (el.dataAttributes && Object.keys(el.dataAttributes).length > 0) {
279
+ text += `\n\nšŸ“¦ Data Attributes: ${JSON.stringify(el.dataAttributes)}`;
280
+ }
281
+ }
282
+
283
+ if (c.hints.searchTerms.length > 0) {
284
+ text += `\n\nšŸ” Search Terms: ${c.hints.searchTerms.join(", ")}`;
285
+ }
286
+
287
+ if (c.hints.possibleFiles.length > 0) {
288
+ text += `\nšŸ“ Possible Files: ${c.hints.possibleFiles.join(", ")}`;
289
+ }
290
+
291
+ if (c.replies && c.replies.length > 0) {
292
+ text += `\n\nšŸ’¬ Thread (${c.replies.length} replies):`;
293
+ c.replies.forEach((r, i) => {
294
+ text += `\n${i + 1}. ${r.authorName}: "${r.content}" (${r.createdAt})`;
295
+ });
296
+ }
297
+
298
+ if (c.designSuggestion) {
299
+ text += `\n\nšŸŽØ Design Suggestion:`;
300
+ c.designSuggestion.changes.forEach(change => {
301
+ text += `\n ${change.property}: ${change.oldValue} → ${change.newValue}`;
302
+ });
303
+ }
304
+
305
+ return {
306
+ content: [{ type: "text", text }],
307
+ structuredContent: { found: true, comment: c },
308
+ };
309
+ } catch (error) {
310
+ const message = error instanceof Error ? error.message : "Unknown error";
311
+ return {
312
+ content: [{ type: "text", text: `Error fetching comment details: ${message}` }],
313
+ structuredContent: { found: false, comment: null },
314
+ isError: true,
315
+ };
316
+ }
317
+ }
318
+ );
319
+
320
+ // Tool 3: Mark feedback as resolved
155
321
  server.registerTool(
156
322
  "mark_feedback_resolved",
157
323
  {
@@ -159,7 +325,7 @@ server.registerTool(
159
325
  description: `Marks a feedback comment as resolved after you've fixed the issue.
160
326
 
161
327
  Call this after making the code changes to update the feedback status.
162
- The comment ID can be obtained from get_open_feedback.
328
+ The comment ID can be obtained from get_feedback_summary.
163
329
 
164
330
  Note: Requires SUPER_FEEDBACK_ADMIN_CODE to be configured.`,
165
331
  inputSchema: {
@@ -191,7 +357,7 @@ Note: Requires SUPER_FEEDBACK_ADMIN_CODE to be configured.`,
191
357
 
192
358
  const output = {
193
359
  success: true,
194
- message: `Comment ${commentId} marked as resolved`,
360
+ message: `āœ… Comment ${commentId} marked as resolved`,
195
361
  };
196
362
 
197
363
  return {
@@ -209,66 +375,6 @@ Note: Requires SUPER_FEEDBACK_ADMIN_CODE to be configured.`,
209
375
  }
210
376
  );
211
377
 
212
- // Tool 3: Get feedback details
213
- server.registerTool(
214
- "get_feedback_details",
215
- {
216
- title: "Get Feedback Details",
217
- description: `Gets full details for a specific feedback comment, including all element data and metadata.
218
-
219
- Use this when you need more context about a specific piece of feedback.`,
220
- inputSchema: {
221
- commentId: z.string().describe("The comment ID to get details for"),
222
- },
223
- outputSchema: {
224
- found: z.boolean(),
225
- comment: z.any().nullable(),
226
- },
227
- },
228
- async ({ commentId }) => {
229
- try {
230
- // Build query params - get all comments and find the specific one
231
- const params = new URLSearchParams();
232
- if (ACCESS_CODE) params.set("accessCode", ACCESS_CODE);
233
- if (ADMIN_CODE) params.set("adminCode", ADMIN_CODE);
234
- params.set("status", "all"); // Get all statuses
235
-
236
- const response = await callConvexAPI<FeedbackResponse>(
237
- `/api/feedback/ai?${params.toString()}`
238
- );
239
-
240
- const comment = response.comments?.find(c => c.id === commentId);
241
-
242
- if (!comment) {
243
- return {
244
- content: [{ type: "text", text: `Comment ${commentId} not found` }],
245
- structuredContent: { found: false, comment: null },
246
- };
247
- }
248
-
249
- const output = {
250
- found: true,
251
- comment: comment,
252
- };
253
-
254
- return {
255
- content: [{
256
- type: "text",
257
- text: `Feedback: "${comment.feedback}"\nElement: <${comment.element?.tag || "unknown"}> "${comment.element?.text || ""}"\nPage: ${comment.page.path}\nSearch terms: ${comment.hints.searchTerms.join(", ")}`,
258
- }],
259
- structuredContent: output,
260
- };
261
- } catch (error) {
262
- const message = error instanceof Error ? error.message : "Unknown error";
263
- return {
264
- content: [{ type: "text", text: `Error: ${message}` }],
265
- structuredContent: { found: false, comment: null },
266
- isError: true,
267
- };
268
- }
269
- }
270
- );
271
-
272
378
  // Start the server
273
379
  async function main() {
274
380
  const transport = new StdioServerTransport();