super-feedback-mcp 0.1.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 +145 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +221 -0
- package/package.json +34 -0
- package/src/index.ts +284 -0
- package/tsconfig.json +16 -0
package/README.md
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
# Super Feedback MCP Server
|
|
2
|
+
|
|
3
|
+
An MCP (Model Context Protocol) server that enables AI agents in Cursor to query and resolve client feedback from Super Feedback projects.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
cd mcp-server
|
|
9
|
+
npm install
|
|
10
|
+
npm run build
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Configuration
|
|
14
|
+
|
|
15
|
+
Add to your Cursor MCP settings (`~/.cursor/mcp.json` or workspace settings):
|
|
16
|
+
|
|
17
|
+
```json
|
|
18
|
+
{
|
|
19
|
+
"mcpServers": {
|
|
20
|
+
"super-feedback": {
|
|
21
|
+
"command": "node",
|
|
22
|
+
"args": ["/path/to/super-feedback/mcp-server/dist/index.js"],
|
|
23
|
+
"env": {
|
|
24
|
+
"SUPER_FEEDBACK_ACCESS_CODE": "your-project-access-code",
|
|
25
|
+
"SUPER_FEEDBACK_ADMIN_CODE": "your-project-admin-code"
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Or if published to npm:
|
|
33
|
+
|
|
34
|
+
```json
|
|
35
|
+
{
|
|
36
|
+
"mcpServers": {
|
|
37
|
+
"super-feedback": {
|
|
38
|
+
"command": "npx",
|
|
39
|
+
"args": ["super-feedback-mcp"],
|
|
40
|
+
"env": {
|
|
41
|
+
"SUPER_FEEDBACK_ACCESS_CODE": "your-project-access-code",
|
|
42
|
+
"SUPER_FEEDBACK_ADMIN_CODE": "your-project-admin-code"
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Environment Variables
|
|
50
|
+
|
|
51
|
+
| Variable | Required | Description |
|
|
52
|
+
|----------|----------|-------------|
|
|
53
|
+
| `SUPER_FEEDBACK_ACCESS_CODE` | Yes* | Your project's access code (for reading feedback) |
|
|
54
|
+
| `SUPER_FEEDBACK_ADMIN_CODE` | Yes* | Your project's admin code (required for marking resolved) |
|
|
55
|
+
| `CONVEX_URL` | No | Custom Convex HTTP URL (defaults to production). Use `.convex.site` domain, not `.convex.cloud` |
|
|
56
|
+
|
|
57
|
+
*At least one of `ACCESS_CODE` or `ADMIN_CODE` is required. For full functionality (read + resolve), provide both.
|
|
58
|
+
|
|
59
|
+
**Where to find these codes:**
|
|
60
|
+
- Go to your Super Feedback dashboard
|
|
61
|
+
- Select your project
|
|
62
|
+
- Click "Settings" or the gear icon
|
|
63
|
+
- Copy the "Access Code" and "Admin Code"
|
|
64
|
+
|
|
65
|
+
## Available Tools
|
|
66
|
+
|
|
67
|
+
### 1. `get_open_feedback`
|
|
68
|
+
|
|
69
|
+
Retrieves all open (unresolved) feedback comments for your project.
|
|
70
|
+
|
|
71
|
+
**Parameters:**
|
|
72
|
+
- `includeResolved` (boolean, optional): Include resolved comments too
|
|
73
|
+
- `pageFilter` (string, optional): Filter to a specific page path (e.g., `/pricing`)
|
|
74
|
+
|
|
75
|
+
**Returns:**
|
|
76
|
+
- 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)
|
|
83
|
+
|
|
84
|
+
### 2. `mark_feedback_resolved`
|
|
85
|
+
|
|
86
|
+
Marks a feedback comment as resolved after you've fixed the issue.
|
|
87
|
+
|
|
88
|
+
**Parameters:**
|
|
89
|
+
- `commentId` (string): The comment ID to mark as resolved
|
|
90
|
+
|
|
91
|
+
### 3. `get_feedback_details`
|
|
92
|
+
|
|
93
|
+
Gets full details for a specific feedback comment.
|
|
94
|
+
|
|
95
|
+
**Parameters:**
|
|
96
|
+
- `commentId` (string): The comment ID to get details for
|
|
97
|
+
|
|
98
|
+
## Usage Example
|
|
99
|
+
|
|
100
|
+
In Cursor, you can ask the AI agent:
|
|
101
|
+
|
|
102
|
+
> "Get all open feedback for my project and fix them"
|
|
103
|
+
|
|
104
|
+
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
|
|
109
|
+
|
|
110
|
+
## How It Works
|
|
111
|
+
|
|
112
|
+
The MCP server connects to your Super Feedback project via the Convex API using your access code. It transforms the raw feedback data into AI-friendly format with:
|
|
113
|
+
|
|
114
|
+
- **Element identification**: Multiple selector strategies (ID, test ID, CSS path, XPath)
|
|
115
|
+
- **Route mapping**: Extracts page path and suggests likely source files
|
|
116
|
+
- **Search hints**: Generates terms to grep for in your codebase
|
|
117
|
+
|
|
118
|
+
## Development
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
# Watch mode
|
|
122
|
+
npm run dev
|
|
123
|
+
|
|
124
|
+
# Build
|
|
125
|
+
npm run build
|
|
126
|
+
|
|
127
|
+
# Test locally
|
|
128
|
+
SUPER_FEEDBACK_ACCESS_CODE=your-access-code \
|
|
129
|
+
SUPER_FEEDBACK_ADMIN_CODE=your-admin-code \
|
|
130
|
+
node dist/index.js
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## Troubleshooting
|
|
134
|
+
|
|
135
|
+
### "ACCESS_CODE environment variable is required"
|
|
136
|
+
|
|
137
|
+
Make sure you've set the `SUPER_FEEDBACK_ACCESS_CODE` in your MCP config's `env` section.
|
|
138
|
+
|
|
139
|
+
### "API error: 401"
|
|
140
|
+
|
|
141
|
+
Your access code is invalid or expired. Check your project settings in Super Feedback.
|
|
142
|
+
|
|
143
|
+
### "API error: 404"
|
|
144
|
+
|
|
145
|
+
The project may have been deleted or the Convex deployment URL is incorrect.
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
+
import * as z from "zod";
|
|
5
|
+
// Configuration from environment
|
|
6
|
+
// Note: HTTP endpoints use .convex.site, not .convex.cloud
|
|
7
|
+
const CONVEX_URL = process.env.CONVEX_URL || "https://energized-eel-335.convex.site";
|
|
8
|
+
const ACCESS_CODE = process.env.SUPER_FEEDBACK_ACCESS_CODE;
|
|
9
|
+
const ADMIN_CODE = process.env.SUPER_FEEDBACK_ADMIN_CODE;
|
|
10
|
+
if (!ACCESS_CODE && !ADMIN_CODE) {
|
|
11
|
+
console.error("Error: Either SUPER_FEEDBACK_ACCESS_CODE or SUPER_FEEDBACK_ADMIN_CODE environment variable is required");
|
|
12
|
+
process.exit(1);
|
|
13
|
+
}
|
|
14
|
+
// API call helper
|
|
15
|
+
async function callConvexAPI(endpoint, options) {
|
|
16
|
+
const url = `${CONVEX_URL}${endpoint}`;
|
|
17
|
+
const response = await fetch(url, {
|
|
18
|
+
...options,
|
|
19
|
+
headers: {
|
|
20
|
+
"Content-Type": "application/json",
|
|
21
|
+
...options?.headers,
|
|
22
|
+
},
|
|
23
|
+
});
|
|
24
|
+
if (!response.ok) {
|
|
25
|
+
const error = await response.text();
|
|
26
|
+
throw new Error(`API error: ${response.status} - ${error}`);
|
|
27
|
+
}
|
|
28
|
+
return response.json();
|
|
29
|
+
}
|
|
30
|
+
// Create MCP server
|
|
31
|
+
const server = new McpServer({
|
|
32
|
+
name: "super-feedback",
|
|
33
|
+
version: "0.1.0",
|
|
34
|
+
});
|
|
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
|
|
45
|
+
|
|
46
|
+
Use this to understand what changes clients have requested.`,
|
|
47
|
+
inputSchema: {
|
|
48
|
+
includeResolved: z.boolean().optional().describe("Include resolved comments too (default: false)"),
|
|
49
|
+
pageFilter: z.string().optional().describe("Filter to specific page path (e.g., '/pricing')"),
|
|
50
|
+
},
|
|
51
|
+
outputSchema: {
|
|
52
|
+
projectName: z.string(),
|
|
53
|
+
totalCount: z.number(),
|
|
54
|
+
comments: z.array(z.object({
|
|
55
|
+
id: z.string(),
|
|
56
|
+
index: z.number(),
|
|
57
|
+
feedback: z.string(),
|
|
58
|
+
author: z.string(),
|
|
59
|
+
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
|
+
}),
|
|
70
|
+
})),
|
|
71
|
+
},
|
|
72
|
+
}, async ({ includeResolved = false, pageFilter }) => {
|
|
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");
|
|
82
|
+
if (pageFilter)
|
|
83
|
+
params.set("page", pageFilter);
|
|
84
|
+
// Use the AI-optimized endpoint
|
|
85
|
+
const response = await callConvexAPI(`/api/feedback/ai?${params.toString()}`);
|
|
86
|
+
const output = {
|
|
87
|
+
projectName: response.project?.name || "Unknown Project",
|
|
88
|
+
totalCount: response.totalCount,
|
|
89
|
+
comments: response.comments,
|
|
90
|
+
};
|
|
91
|
+
// 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
|
+
return {
|
|
94
|
+
content: [{
|
|
95
|
+
type: "text",
|
|
96
|
+
text: `Found ${response.totalCount} feedback item(s):\n\n${summary}\n\nFull data available in structuredContent.`,
|
|
97
|
+
}],
|
|
98
|
+
structuredContent: output,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
catch (error) {
|
|
102
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
103
|
+
return {
|
|
104
|
+
content: [{ type: "text", text: `Error fetching feedback: ${message}` }],
|
|
105
|
+
isError: true,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
// Tool 2: Mark feedback as resolved
|
|
110
|
+
server.registerTool("mark_feedback_resolved", {
|
|
111
|
+
title: "Mark Feedback Resolved",
|
|
112
|
+
description: `Marks a feedback comment as resolved after you've fixed the issue.
|
|
113
|
+
|
|
114
|
+
Call this after making the code changes to update the feedback status.
|
|
115
|
+
The comment ID can be obtained from get_open_feedback.
|
|
116
|
+
|
|
117
|
+
Note: Requires SUPER_FEEDBACK_ADMIN_CODE to be configured.`,
|
|
118
|
+
inputSchema: {
|
|
119
|
+
commentId: z.string().describe("The comment ID to mark as resolved"),
|
|
120
|
+
},
|
|
121
|
+
outputSchema: {
|
|
122
|
+
success: z.boolean(),
|
|
123
|
+
message: z.string(),
|
|
124
|
+
},
|
|
125
|
+
}, async ({ commentId }) => {
|
|
126
|
+
if (!ADMIN_CODE) {
|
|
127
|
+
return {
|
|
128
|
+
content: [{ type: "text", text: "Error: SUPER_FEEDBACK_ADMIN_CODE is required to update feedback status" }],
|
|
129
|
+
structuredContent: { success: false, message: "Admin code not configured" },
|
|
130
|
+
isError: true,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
try {
|
|
134
|
+
await callConvexAPI(`/api/feedback/ai/status`, {
|
|
135
|
+
method: "POST",
|
|
136
|
+
body: JSON.stringify({
|
|
137
|
+
adminCode: ADMIN_CODE,
|
|
138
|
+
commentId,
|
|
139
|
+
status: "resolved",
|
|
140
|
+
}),
|
|
141
|
+
});
|
|
142
|
+
const output = {
|
|
143
|
+
success: true,
|
|
144
|
+
message: `Comment ${commentId} marked as resolved`,
|
|
145
|
+
};
|
|
146
|
+
return {
|
|
147
|
+
content: [{ type: "text", text: output.message }],
|
|
148
|
+
structuredContent: output,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
catch (error) {
|
|
152
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
153
|
+
return {
|
|
154
|
+
content: [{ type: "text", text: `Error: ${message}` }],
|
|
155
|
+
structuredContent: { success: false, message },
|
|
156
|
+
isError: true,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
});
|
|
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
|
+
// Start the server
|
|
212
|
+
async function main() {
|
|
213
|
+
const transport = new StdioServerTransport();
|
|
214
|
+
await server.connect(transport);
|
|
215
|
+
// Log to stderr so it doesn't interfere with MCP protocol on stdout
|
|
216
|
+
console.error("Super Feedback MCP server running...");
|
|
217
|
+
}
|
|
218
|
+
main().catch((error) => {
|
|
219
|
+
console.error("Fatal error:", error);
|
|
220
|
+
process.exit(1);
|
|
221
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "super-feedback-mcp",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "MCP server for Super Feedback - enables AI agents to query and resolve client feedback",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"super-feedback-mcp": "dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsc",
|
|
12
|
+
"dev": "tsc --watch",
|
|
13
|
+
"start": "node dist/index.js",
|
|
14
|
+
"prepare": "npm run build"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"mcp",
|
|
18
|
+
"feedback",
|
|
19
|
+
"cursor",
|
|
20
|
+
"ai-agent"
|
|
21
|
+
],
|
|
22
|
+
"license": "MIT",
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
25
|
+
"zod": "^3.24.0"
|
|
26
|
+
},
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"@types/node": "^22.10.0",
|
|
29
|
+
"typescript": "^5.7.2"
|
|
30
|
+
},
|
|
31
|
+
"engines": {
|
|
32
|
+
"node": ">=18"
|
|
33
|
+
}
|
|
34
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
+
import * as z from "zod";
|
|
5
|
+
|
|
6
|
+
// Types for API responses
|
|
7
|
+
interface FeedbackComment {
|
|
8
|
+
id: string;
|
|
9
|
+
index: number;
|
|
10
|
+
feedback: string;
|
|
11
|
+
author: string;
|
|
12
|
+
status: string;
|
|
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 };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
interface FeedbackResponse {
|
|
30
|
+
project: { id: string; name: string; url: string };
|
|
31
|
+
isAdmin?: boolean;
|
|
32
|
+
totalCount: number;
|
|
33
|
+
comments: FeedbackComment[];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Configuration from environment
|
|
37
|
+
// Note: HTTP endpoints use .convex.site, not .convex.cloud
|
|
38
|
+
const CONVEX_URL = process.env.CONVEX_URL || "https://energized-eel-335.convex.site";
|
|
39
|
+
const ACCESS_CODE = process.env.SUPER_FEEDBACK_ACCESS_CODE;
|
|
40
|
+
const ADMIN_CODE = process.env.SUPER_FEEDBACK_ADMIN_CODE;
|
|
41
|
+
|
|
42
|
+
if (!ACCESS_CODE && !ADMIN_CODE) {
|
|
43
|
+
console.error("Error: Either SUPER_FEEDBACK_ACCESS_CODE or SUPER_FEEDBACK_ADMIN_CODE environment variable is required");
|
|
44
|
+
process.exit(1);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// API call helper
|
|
48
|
+
async function callConvexAPI<T>(endpoint: string, options?: RequestInit): Promise<T> {
|
|
49
|
+
const url = `${CONVEX_URL}${endpoint}`;
|
|
50
|
+
const response = await fetch(url, {
|
|
51
|
+
...options,
|
|
52
|
+
headers: {
|
|
53
|
+
"Content-Type": "application/json",
|
|
54
|
+
...options?.headers,
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
if (!response.ok) {
|
|
59
|
+
const error = await response.text();
|
|
60
|
+
throw new Error(`API error: ${response.status} - ${error}`);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return response.json();
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Create MCP server
|
|
67
|
+
const server = new McpServer({
|
|
68
|
+
name: "super-feedback",
|
|
69
|
+
version: "0.1.0",
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// Tool 1: Get open feedback
|
|
73
|
+
server.registerTool(
|
|
74
|
+
"get_open_feedback",
|
|
75
|
+
{
|
|
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
|
|
84
|
+
|
|
85
|
+
Use this to understand what changes clients have requested.`,
|
|
86
|
+
inputSchema: {
|
|
87
|
+
includeResolved: z.boolean().optional().describe("Include resolved comments too (default: false)"),
|
|
88
|
+
pageFilter: z.string().optional().describe("Filter to specific page path (e.g., '/pricing')"),
|
|
89
|
+
},
|
|
90
|
+
outputSchema: {
|
|
91
|
+
projectName: z.string(),
|
|
92
|
+
totalCount: z.number(),
|
|
93
|
+
comments: z.array(z.object({
|
|
94
|
+
id: z.string(),
|
|
95
|
+
index: z.number(),
|
|
96
|
+
feedback: z.string(),
|
|
97
|
+
author: z.string(),
|
|
98
|
+
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
|
+
}),
|
|
109
|
+
})),
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
async ({ includeResolved = false, pageFilter }) => {
|
|
113
|
+
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");
|
|
119
|
+
if (pageFilter) params.set("page", pageFilter);
|
|
120
|
+
|
|
121
|
+
// Use the AI-optimized endpoint
|
|
122
|
+
const response = await callConvexAPI<FeedbackResponse>(
|
|
123
|
+
`/api/feedback/ai?${params.toString()}`
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
const output = {
|
|
127
|
+
projectName: response.project?.name || "Unknown Project",
|
|
128
|
+
totalCount: response.totalCount,
|
|
129
|
+
comments: response.comments,
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
// 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");
|
|
136
|
+
|
|
137
|
+
return {
|
|
138
|
+
content: [{
|
|
139
|
+
type: "text",
|
|
140
|
+
text: `Found ${response.totalCount} feedback item(s):\n\n${summary}\n\nFull data available in structuredContent.`,
|
|
141
|
+
}],
|
|
142
|
+
structuredContent: output,
|
|
143
|
+
};
|
|
144
|
+
} catch (error) {
|
|
145
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
146
|
+
return {
|
|
147
|
+
content: [{ type: "text", text: `Error fetching feedback: ${message}` }],
|
|
148
|
+
isError: true,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
// Tool 2: Mark feedback as resolved
|
|
155
|
+
server.registerTool(
|
|
156
|
+
"mark_feedback_resolved",
|
|
157
|
+
{
|
|
158
|
+
title: "Mark Feedback Resolved",
|
|
159
|
+
description: `Marks a feedback comment as resolved after you've fixed the issue.
|
|
160
|
+
|
|
161
|
+
Call this after making the code changes to update the feedback status.
|
|
162
|
+
The comment ID can be obtained from get_open_feedback.
|
|
163
|
+
|
|
164
|
+
Note: Requires SUPER_FEEDBACK_ADMIN_CODE to be configured.`,
|
|
165
|
+
inputSchema: {
|
|
166
|
+
commentId: z.string().describe("The comment ID to mark as resolved"),
|
|
167
|
+
},
|
|
168
|
+
outputSchema: {
|
|
169
|
+
success: z.boolean(),
|
|
170
|
+
message: z.string(),
|
|
171
|
+
},
|
|
172
|
+
},
|
|
173
|
+
async ({ commentId }) => {
|
|
174
|
+
if (!ADMIN_CODE) {
|
|
175
|
+
return {
|
|
176
|
+
content: [{ type: "text", text: "Error: SUPER_FEEDBACK_ADMIN_CODE is required to update feedback status" }],
|
|
177
|
+
structuredContent: { success: false, message: "Admin code not configured" },
|
|
178
|
+
isError: true,
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
try {
|
|
183
|
+
await callConvexAPI(`/api/feedback/ai/status`, {
|
|
184
|
+
method: "POST",
|
|
185
|
+
body: JSON.stringify({
|
|
186
|
+
adminCode: ADMIN_CODE,
|
|
187
|
+
commentId,
|
|
188
|
+
status: "resolved",
|
|
189
|
+
}),
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
const output = {
|
|
193
|
+
success: true,
|
|
194
|
+
message: `Comment ${commentId} marked as resolved`,
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
return {
|
|
198
|
+
content: [{ type: "text", text: output.message }],
|
|
199
|
+
structuredContent: output,
|
|
200
|
+
};
|
|
201
|
+
} catch (error) {
|
|
202
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
203
|
+
return {
|
|
204
|
+
content: [{ type: "text", text: `Error: ${message}` }],
|
|
205
|
+
structuredContent: { success: false, message },
|
|
206
|
+
isError: true,
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
);
|
|
211
|
+
|
|
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
|
+
// Start the server
|
|
273
|
+
async function main() {
|
|
274
|
+
const transport = new StdioServerTransport();
|
|
275
|
+
await server.connect(transport);
|
|
276
|
+
|
|
277
|
+
// Log to stderr so it doesn't interfere with MCP protocol on stdout
|
|
278
|
+
console.error("Super Feedback MCP server running...");
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
main().catch((error) => {
|
|
282
|
+
console.error("Fatal error:", error);
|
|
283
|
+
process.exit(1);
|
|
284
|
+
});
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "NodeNext",
|
|
5
|
+
"moduleResolution": "NodeNext",
|
|
6
|
+
"esModuleInterop": true,
|
|
7
|
+
"strict": true,
|
|
8
|
+
"skipLibCheck": true,
|
|
9
|
+
"outDir": "dist",
|
|
10
|
+
"rootDir": "src",
|
|
11
|
+
"declaration": true,
|
|
12
|
+
"resolveJsonModule": true
|
|
13
|
+
},
|
|
14
|
+
"include": ["src/**/*"],
|
|
15
|
+
"exclude": ["node_modules", "dist"]
|
|
16
|
+
}
|