feedbackbasket-mcp-server 1.0.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 +192 -0
- package/dist/client.d.ts +60 -0
- package/dist/client.js +207 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +230 -0
- package/dist/types.d.ts +117 -0
- package/dist/types.js +2 -0
- package/package.json +49 -0
package/README.md
ADDED
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
# FeedbackBasket MCP Server
|
|
2
|
+
|
|
3
|
+
Model Context Protocol (MCP) server for FeedbackBasket that allows AI assistants to query your project feedback and bug reports.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- 🔍 **List Projects** - View all your FeedbackBasket projects with summary statistics
|
|
8
|
+
- 📝 **Get Feedback** - Retrieve feedback with filtering by category, status, sentiment, and more
|
|
9
|
+
- 🐛 **Get Bug Reports** - Specifically fetch bug reports with severity classification
|
|
10
|
+
- 🔎 **Search Feedback** - Search across all feedback content using text queries
|
|
11
|
+
- 🔐 **Secure Authentication** - Uses API keys with project-level access control
|
|
12
|
+
- 📊 **Rich Statistics** - Get counts by category, status, sentiment, and severity
|
|
13
|
+
|
|
14
|
+
## Installation & Setup
|
|
15
|
+
|
|
16
|
+
### 1. Generate API Key
|
|
17
|
+
|
|
18
|
+
First, you'll need to create an API key from your FeedbackBasket account:
|
|
19
|
+
|
|
20
|
+
1. **Visit** [feedbackbasket.com](https://feedbackbasket.com) and log into your account
|
|
21
|
+
2. **Navigate** to your Account Settings (click your profile → Account)
|
|
22
|
+
3. **Find** the "MCP API Keys" section
|
|
23
|
+
4. **Click** "New API Key"
|
|
24
|
+
5. **Name your key** (e.g., "Claude Desktop", "Cursor IDE", "Windsurf")
|
|
25
|
+
6. **Select projects** you want to grant access to (or leave empty for all projects)
|
|
26
|
+
7. **Copy the generated API key** (starts with `fb_key_`) - you'll only see the full key once!
|
|
27
|
+
|
|
28
|
+
> 💡 **Tip**: You can manage your API keys anytime from your account settings at [feedbackbasket.com/account](https://feedbackbasket.com/account)
|
|
29
|
+
|
|
30
|
+
### 2. Configure Your Editor
|
|
31
|
+
|
|
32
|
+
Add the MCP server to your editor's configuration:
|
|
33
|
+
|
|
34
|
+
#### Quick Setup (All Editors)
|
|
35
|
+
```json
|
|
36
|
+
{
|
|
37
|
+
"mcpServers": {
|
|
38
|
+
"feedbackbasket": {
|
|
39
|
+
"command": "npx",
|
|
40
|
+
"args": ["-y", "@feedbackbasket/mcp-server@latest", "--api-key", "fb_key_your_api_key_here"]
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
#### Detailed Examples
|
|
47
|
+
|
|
48
|
+
#### Claude Desktop
|
|
49
|
+
```json
|
|
50
|
+
{
|
|
51
|
+
"mcpServers": {
|
|
52
|
+
"feedbackbasket": {
|
|
53
|
+
"command": "npx",
|
|
54
|
+
"args": [
|
|
55
|
+
"-y",
|
|
56
|
+
"@feedbackbasket/mcp-server@latest",
|
|
57
|
+
"--api-key",
|
|
58
|
+
"fb_key_your_api_key_here"
|
|
59
|
+
]
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
#### Cursor/Windsurf
|
|
66
|
+
```json
|
|
67
|
+
{
|
|
68
|
+
"mcpServers": {
|
|
69
|
+
"feedbackbasket": {
|
|
70
|
+
"command": "npx",
|
|
71
|
+
"args": [
|
|
72
|
+
"-y",
|
|
73
|
+
"@feedbackbasket/mcp-server@latest",
|
|
74
|
+
"--api-key",
|
|
75
|
+
"fb_key_your_api_key_here"
|
|
76
|
+
]
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
#### Environment Variable (Alternative)
|
|
83
|
+
```json
|
|
84
|
+
{
|
|
85
|
+
"mcpServers": {
|
|
86
|
+
"feedbackbasket": {
|
|
87
|
+
"command": "npx",
|
|
88
|
+
"args": ["-y", "@feedbackbasket/mcp-server@latest"],
|
|
89
|
+
"env": {
|
|
90
|
+
"FEEDBACKBASKET_API_KEY": "fb_key_your_api_key_here"
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Usage Examples
|
|
98
|
+
|
|
99
|
+
Once configured, you can ask your AI assistant:
|
|
100
|
+
|
|
101
|
+
### Project Overview
|
|
102
|
+
- "Show me all my FeedbackBasket projects"
|
|
103
|
+
- "What projects do I have and how much feedback do they have?"
|
|
104
|
+
|
|
105
|
+
### Bug Reports
|
|
106
|
+
- "Show me all bug reports from my projects"
|
|
107
|
+
- "Get high severity bug reports that are still pending"
|
|
108
|
+
- "Find bug reports for my main website project"
|
|
109
|
+
|
|
110
|
+
### Feedback Analysis
|
|
111
|
+
- "Show me negative feedback from the last week"
|
|
112
|
+
- "Get all feature requests from my mobile app project"
|
|
113
|
+
- "Search for feedback containing 'login error'"
|
|
114
|
+
|
|
115
|
+
### Filtering & Search
|
|
116
|
+
- "Show me reviewed feedback with positive sentiment"
|
|
117
|
+
- "Find all pending bug reports with high severity"
|
|
118
|
+
- "Search for feedback about 'payment issues' in my e-commerce project"
|
|
119
|
+
|
|
120
|
+
## Available Tools
|
|
121
|
+
|
|
122
|
+
### `list_projects`
|
|
123
|
+
Lists all projects accessible by your API key with summary statistics.
|
|
124
|
+
|
|
125
|
+
### `get_feedback`
|
|
126
|
+
Retrieves feedback with optional filtering:
|
|
127
|
+
- `projectId` - Filter by specific project
|
|
128
|
+
- `category` - Filter by BUG, FEATURE, or REVIEW
|
|
129
|
+
- `status` - Filter by PENDING, REVIEWED, or DONE
|
|
130
|
+
- `sentiment` - Filter by POSITIVE, NEGATIVE, or NEUTRAL
|
|
131
|
+
- `search` - Text search in feedback content
|
|
132
|
+
- `limit` - Number of results (max 100)
|
|
133
|
+
- `includeNotes` - Include internal notes
|
|
134
|
+
|
|
135
|
+
### `get_bug_reports`
|
|
136
|
+
Specifically fetches bug reports with computed severity:
|
|
137
|
+
- `projectId` - Filter by specific project
|
|
138
|
+
- `status` - Filter by bug status
|
|
139
|
+
- `severity` - Filter by high, medium, or low severity
|
|
140
|
+
- `search` - Text search in bug reports
|
|
141
|
+
- `limit` - Number of results (max 100)
|
|
142
|
+
- `includeNotes` - Include internal notes
|
|
143
|
+
|
|
144
|
+
### `search_feedback`
|
|
145
|
+
Searches feedback content across all accessible projects:
|
|
146
|
+
- `query` - Search text (required)
|
|
147
|
+
- `projectId` - Limit to specific project
|
|
148
|
+
- `category` - Filter by category
|
|
149
|
+
- `limit` - Number of results (max 50)
|
|
150
|
+
|
|
151
|
+
## Security & Privacy
|
|
152
|
+
|
|
153
|
+
- ✅ **Read-only access** - MCP server can only read data, never modify
|
|
154
|
+
- ✅ **Project-level permissions** - Grant access only to specific projects
|
|
155
|
+
- ✅ **API key authentication** - Secure token-based authentication
|
|
156
|
+
- ✅ **Usage tracking** - Monitor API key usage in your dashboard
|
|
157
|
+
- ✅ **Revokable access** - Instantly deactivate API keys if needed
|
|
158
|
+
- ✅ **Subscription limits respected** - Only returns feedback within your plan limits
|
|
159
|
+
|
|
160
|
+
## Troubleshooting
|
|
161
|
+
|
|
162
|
+
### "Invalid or missing API key"
|
|
163
|
+
- Check that your API key starts with `fb_key_`
|
|
164
|
+
- Ensure the API key is still active in your dashboard
|
|
165
|
+
- Verify the API key has access to at least one project
|
|
166
|
+
|
|
167
|
+
### "No projects found"
|
|
168
|
+
- Make sure your API key has been granted access to projects
|
|
169
|
+
- Check that you have projects in your FeedbackBasket account
|
|
170
|
+
|
|
171
|
+
### "Authentication failed"
|
|
172
|
+
- Verify your API key hasn't been revoked
|
|
173
|
+
- Check that the API key format is correct (71 characters, starts with `fb_key_`)
|
|
174
|
+
|
|
175
|
+
### Installation Issues
|
|
176
|
+
- Ensure you have Node.js installed
|
|
177
|
+
- Try clearing npx cache: `npx clear-npx-cache`
|
|
178
|
+
- Check your internet connection for package downloads
|
|
179
|
+
|
|
180
|
+
## API Key Management
|
|
181
|
+
|
|
182
|
+
Visit your [FeedbackBasket dashboard](https://feedbackbasket.com/dashboard/settings) to:
|
|
183
|
+
- Generate new API keys
|
|
184
|
+
- Manage project access for existing keys
|
|
185
|
+
- View usage statistics
|
|
186
|
+
- Deactivate or delete keys
|
|
187
|
+
- Monitor key activity
|
|
188
|
+
|
|
189
|
+
## Support
|
|
190
|
+
|
|
191
|
+
- 🐛 Issues: [GitHub Issues](https://github.com/deifos/feedbackbasket-mcp/issues)
|
|
192
|
+
- 📖 Docs: [FeedbackBasket Documentation](https://feedbackbasket.com/docs)
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
export declare class FeedbackBasketClient {
|
|
2
|
+
private api;
|
|
3
|
+
constructor(apiKey: string, baseUrl?: string);
|
|
4
|
+
/**
|
|
5
|
+
* List all projects accessible by the API key
|
|
6
|
+
*/
|
|
7
|
+
listProjects(): Promise<{
|
|
8
|
+
content: Array<{
|
|
9
|
+
type: string;
|
|
10
|
+
text: string;
|
|
11
|
+
}>;
|
|
12
|
+
}>;
|
|
13
|
+
/**
|
|
14
|
+
* Get feedback for projects
|
|
15
|
+
*/
|
|
16
|
+
getFeedback(params?: {
|
|
17
|
+
projectId?: string;
|
|
18
|
+
category?: 'BUG' | 'FEATURE' | 'REVIEW';
|
|
19
|
+
status?: 'PENDING' | 'REVIEWED' | 'DONE';
|
|
20
|
+
sentiment?: 'POSITIVE' | 'NEGATIVE' | 'NEUTRAL';
|
|
21
|
+
limit?: number;
|
|
22
|
+
search?: string;
|
|
23
|
+
includeNotes?: boolean;
|
|
24
|
+
}): Promise<{
|
|
25
|
+
content: Array<{
|
|
26
|
+
type: string;
|
|
27
|
+
text: string;
|
|
28
|
+
}>;
|
|
29
|
+
}>;
|
|
30
|
+
/**
|
|
31
|
+
* Get bug reports specifically
|
|
32
|
+
*/
|
|
33
|
+
getBugReports(params?: {
|
|
34
|
+
projectId?: string;
|
|
35
|
+
status?: 'PENDING' | 'REVIEWED' | 'DONE';
|
|
36
|
+
severity?: 'high' | 'medium' | 'low';
|
|
37
|
+
limit?: number;
|
|
38
|
+
search?: string;
|
|
39
|
+
includeNotes?: boolean;
|
|
40
|
+
}): Promise<{
|
|
41
|
+
content: Array<{
|
|
42
|
+
type: string;
|
|
43
|
+
text: string;
|
|
44
|
+
}>;
|
|
45
|
+
}>;
|
|
46
|
+
/**
|
|
47
|
+
* Search feedback across all accessible projects
|
|
48
|
+
*/
|
|
49
|
+
searchFeedback(query: string, options?: {
|
|
50
|
+
projectId?: string;
|
|
51
|
+
category?: 'BUG' | 'FEATURE' | 'REVIEW';
|
|
52
|
+
limit?: number;
|
|
53
|
+
}): Promise<{
|
|
54
|
+
content: Array<{
|
|
55
|
+
type: string;
|
|
56
|
+
text: string;
|
|
57
|
+
}>;
|
|
58
|
+
}>;
|
|
59
|
+
private handleError;
|
|
60
|
+
}
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import axios, { AxiosError } from 'axios';
|
|
2
|
+
export class FeedbackBasketClient {
|
|
3
|
+
api;
|
|
4
|
+
constructor(apiKey, baseUrl = 'https://feedbackbasket.com') {
|
|
5
|
+
this.api = axios.create({
|
|
6
|
+
baseURL: `${baseUrl}/api/mcp`,
|
|
7
|
+
headers: {
|
|
8
|
+
'Authorization': `Bearer ${apiKey}`,
|
|
9
|
+
'Content-Type': 'application/json',
|
|
10
|
+
'User-Agent': 'FeedbackBasket-MCP/1.0.0',
|
|
11
|
+
},
|
|
12
|
+
timeout: 30000, // 30 second timeout
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* List all projects accessible by the API key
|
|
17
|
+
*/
|
|
18
|
+
async listProjects() {
|
|
19
|
+
try {
|
|
20
|
+
const response = await this.api.post('/projects', {});
|
|
21
|
+
const projects = response.data.projects;
|
|
22
|
+
if (projects.length === 0) {
|
|
23
|
+
return {
|
|
24
|
+
content: [{
|
|
25
|
+
type: 'text',
|
|
26
|
+
text: 'No projects found. Make sure your API key has access to projects in your FeedbackBasket dashboard.'
|
|
27
|
+
}]
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
const projectList = projects.map(project => {
|
|
31
|
+
const totalFeedback = project.stats.totalFeedback;
|
|
32
|
+
const pendingCount = project.stats.byStatus.PENDING;
|
|
33
|
+
const bugCount = project.stats.byCategory.BUG;
|
|
34
|
+
return [
|
|
35
|
+
`**${project.name}**`,
|
|
36
|
+
` URL: ${project.url}`,
|
|
37
|
+
` Total Feedback: ${totalFeedback}`,
|
|
38
|
+
` Pending: ${pendingCount} | Bugs: ${bugCount}`,
|
|
39
|
+
` Created: ${new Date(project.createdAt).toLocaleDateString()}`,
|
|
40
|
+
''
|
|
41
|
+
].join('\n');
|
|
42
|
+
}).join('\n');
|
|
43
|
+
const summary = [
|
|
44
|
+
`# FeedbackBasket Projects (${projects.length} total)\n`,
|
|
45
|
+
projectList,
|
|
46
|
+
`\n*API Key: ${response.data.apiKeyInfo.name} (${response.data.apiKeyInfo.usageCount} uses)*`
|
|
47
|
+
].join('\n');
|
|
48
|
+
return {
|
|
49
|
+
content: [{
|
|
50
|
+
type: 'text',
|
|
51
|
+
text: summary
|
|
52
|
+
}]
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
56
|
+
throw this.handleError('Failed to fetch projects', error);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Get feedback for projects
|
|
61
|
+
*/
|
|
62
|
+
async getFeedback(params = {}) {
|
|
63
|
+
try {
|
|
64
|
+
const response = await this.api.post('/feedback', {
|
|
65
|
+
limit: 20,
|
|
66
|
+
includeNotes: false,
|
|
67
|
+
...params,
|
|
68
|
+
});
|
|
69
|
+
const feedback = response.data.feedback;
|
|
70
|
+
if (feedback.length === 0) {
|
|
71
|
+
const filters = Object.entries(params)
|
|
72
|
+
.filter(([_, value]) => value !== undefined)
|
|
73
|
+
.map(([key, value]) => `${key}: ${value}`)
|
|
74
|
+
.join(', ');
|
|
75
|
+
return {
|
|
76
|
+
content: [{
|
|
77
|
+
type: 'text',
|
|
78
|
+
text: `No feedback found${filters ? ` with filters: ${filters}` : ''}.`
|
|
79
|
+
}]
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
const feedbackList = feedback.map(item => {
|
|
83
|
+
const category = item.category || 'UNCATEGORIZED';
|
|
84
|
+
const sentiment = item.sentiment || 'UNKNOWN';
|
|
85
|
+
const confidenceText = item.categoryConfidence
|
|
86
|
+
? ` (${Math.round(item.categoryConfidence * 100)}% confidence)`
|
|
87
|
+
: '';
|
|
88
|
+
return [
|
|
89
|
+
`**${category}${confidenceText} | ${sentiment} | ${item.status}**`,
|
|
90
|
+
`Project: ${item.project.name}`,
|
|
91
|
+
`Content: ${item.content.length > 100 ? item.content.substring(0, 100) + '...' : item.content}`,
|
|
92
|
+
item.email ? `Email: ${item.email}` : '',
|
|
93
|
+
item.notes && params.includeNotes ? `Notes: ${item.notes}` : '',
|
|
94
|
+
`Created: ${new Date(item.createdAt).toLocaleDateString()}`,
|
|
95
|
+
''
|
|
96
|
+
].filter(Boolean).join('\n');
|
|
97
|
+
}).join('\n');
|
|
98
|
+
const summary = [
|
|
99
|
+
`# Feedback Results (${feedback.length} of ${response.data.pagination.totalCount})\n`,
|
|
100
|
+
feedbackList,
|
|
101
|
+
response.data.pagination.hasMore ? `\n*Showing first ${feedback.length} results. Use offset parameter to get more.*` : '',
|
|
102
|
+
`\n*API Key: ${response.data.apiKeyInfo.name}*`
|
|
103
|
+
].join('\n');
|
|
104
|
+
return {
|
|
105
|
+
content: [{
|
|
106
|
+
type: 'text',
|
|
107
|
+
text: summary
|
|
108
|
+
}]
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
catch (error) {
|
|
112
|
+
throw this.handleError('Failed to fetch feedback', error);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Get bug reports specifically
|
|
117
|
+
*/
|
|
118
|
+
async getBugReports(params = {}) {
|
|
119
|
+
try {
|
|
120
|
+
const response = await this.api.post('/feedback/bugs', {
|
|
121
|
+
limit: 20,
|
|
122
|
+
includeNotes: false,
|
|
123
|
+
...params,
|
|
124
|
+
});
|
|
125
|
+
const bugReports = response.data.bugReports;
|
|
126
|
+
if (bugReports.length === 0) {
|
|
127
|
+
const filters = Object.entries(params)
|
|
128
|
+
.filter(([_, value]) => value !== undefined)
|
|
129
|
+
.map(([key, value]) => `${key}: ${value}`)
|
|
130
|
+
.join(', ');
|
|
131
|
+
return {
|
|
132
|
+
content: [{
|
|
133
|
+
type: 'text',
|
|
134
|
+
text: `No bug reports found${filters ? ` with filters: ${filters}` : ''}.`
|
|
135
|
+
}]
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
const bugList = bugReports.map(bug => {
|
|
139
|
+
const severityEmoji = bug.severity === 'high' ? '🔴' : bug.severity === 'medium' ? '🟡' : '🟢';
|
|
140
|
+
const statusEmoji = bug.status === 'PENDING' ? '⏳' : bug.status === 'REVIEWED' ? '👁️' : '✅';
|
|
141
|
+
return [
|
|
142
|
+
`${severityEmoji} **${bug.severity.toUpperCase()} SEVERITY** ${statusEmoji} ${bug.status}`,
|
|
143
|
+
`Project: ${bug.project.name}`,
|
|
144
|
+
`Bug: ${bug.content.length > 150 ? bug.content.substring(0, 150) + '...' : bug.content}`,
|
|
145
|
+
bug.email ? `Reported by: ${bug.email}` : '',
|
|
146
|
+
bug.notes && params.includeNotes ? `Notes: ${bug.notes}` : '',
|
|
147
|
+
`Reported: ${new Date(bug.createdAt).toLocaleDateString()}`,
|
|
148
|
+
''
|
|
149
|
+
].filter(Boolean).join('\n');
|
|
150
|
+
}).join('\n');
|
|
151
|
+
const stats = response.data.stats;
|
|
152
|
+
const statsText = [
|
|
153
|
+
`## Bug Statistics`,
|
|
154
|
+
`Total Bugs: ${stats.totalBugs}`,
|
|
155
|
+
`🔴 High: ${stats.bySeverity.high} | 🟡 Medium: ${stats.bySeverity.medium} | 🟢 Low: ${stats.bySeverity.low}`,
|
|
156
|
+
`⏳ Pending: ${stats.byStatus.pending} | 👁️ Reviewed: ${stats.byStatus.reviewed} | ✅ Done: ${stats.byStatus.done}`,
|
|
157
|
+
''
|
|
158
|
+
].join('\n');
|
|
159
|
+
const summary = [
|
|
160
|
+
`# Bug Reports (${bugReports.length} of ${response.data.pagination.totalCount})\n`,
|
|
161
|
+
statsText,
|
|
162
|
+
bugList,
|
|
163
|
+
response.data.pagination.hasMore ? `\n*Showing first ${bugReports.length} results. Use offset parameter to get more.*` : '',
|
|
164
|
+
`\n*API Key: ${response.data.apiKeyInfo.name}*`
|
|
165
|
+
].join('\n');
|
|
166
|
+
return {
|
|
167
|
+
content: [{
|
|
168
|
+
type: 'text',
|
|
169
|
+
text: summary
|
|
170
|
+
}]
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
catch (error) {
|
|
174
|
+
throw this.handleError('Failed to fetch bug reports', error);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Search feedback across all accessible projects
|
|
179
|
+
*/
|
|
180
|
+
async searchFeedback(query, options = {}) {
|
|
181
|
+
return this.getFeedback({
|
|
182
|
+
search: query,
|
|
183
|
+
limit: options.limit || 10,
|
|
184
|
+
...(options.projectId && { projectId: options.projectId }),
|
|
185
|
+
...(options.category && { category: options.category }),
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
handleError(message, error) {
|
|
189
|
+
if (error instanceof AxiosError) {
|
|
190
|
+
const status = error.response?.status;
|
|
191
|
+
const responseMessage = error.response?.data?.message || error.message;
|
|
192
|
+
if (status === 401) {
|
|
193
|
+
return new Error(`Authentication failed: ${responseMessage}. Check your API key.`);
|
|
194
|
+
}
|
|
195
|
+
else if (status === 403) {
|
|
196
|
+
return new Error(`Access denied: ${responseMessage}. Check your API key permissions.`);
|
|
197
|
+
}
|
|
198
|
+
else if (status === 429) {
|
|
199
|
+
return new Error(`Rate limit exceeded: ${responseMessage}. Please try again later.`);
|
|
200
|
+
}
|
|
201
|
+
else {
|
|
202
|
+
return new Error(`${message}: ${responseMessage} (HTTP ${status})`);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
return new Error(`${message}: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
206
|
+
}
|
|
207
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
3
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
4
|
+
import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
|
|
5
|
+
import { program } from 'commander';
|
|
6
|
+
import { FeedbackBasketClient } from './client.js';
|
|
7
|
+
// Parse command line arguments
|
|
8
|
+
program
|
|
9
|
+
.option('--api-key <key>', 'FeedbackBasket API key')
|
|
10
|
+
.option('--base-url <url>', 'Base URL for FeedbackBasket API', 'https://feedbackbasket.com')
|
|
11
|
+
.parse();
|
|
12
|
+
const options = program.opts();
|
|
13
|
+
const apiKey = options.apiKey || process.env.FEEDBACKBASKET_API_KEY;
|
|
14
|
+
if (!apiKey) {
|
|
15
|
+
console.error('Error: API key required.');
|
|
16
|
+
console.error('Usage: Use --api-key option or set FEEDBACKBASKET_API_KEY environment variable');
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
19
|
+
// Validate API key format
|
|
20
|
+
if (!/^fb_key_[a-f0-9]{64}$/.test(apiKey)) {
|
|
21
|
+
console.error('Error: Invalid API key format. API keys should start with "fb_key_" followed by 64 hexadecimal characters.');
|
|
22
|
+
process.exit(1);
|
|
23
|
+
}
|
|
24
|
+
const client = new FeedbackBasketClient(apiKey, options.baseUrl);
|
|
25
|
+
// Create MCP server
|
|
26
|
+
const server = new Server({
|
|
27
|
+
name: 'feedbackbasket-mcp',
|
|
28
|
+
version: '1.0.0',
|
|
29
|
+
capabilities: {
|
|
30
|
+
tools: {},
|
|
31
|
+
},
|
|
32
|
+
});
|
|
33
|
+
// List available tools
|
|
34
|
+
server.setRequestHandler(ListToolsRequestSchema, async (_request) => {
|
|
35
|
+
return {
|
|
36
|
+
tools: [
|
|
37
|
+
{
|
|
38
|
+
name: 'list_projects',
|
|
39
|
+
description: 'List all FeedbackBasket projects accessible by your API key with summary statistics',
|
|
40
|
+
inputSchema: {
|
|
41
|
+
type: 'object',
|
|
42
|
+
properties: {},
|
|
43
|
+
additionalProperties: false,
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
name: 'get_feedback',
|
|
48
|
+
description: 'Get feedback from your FeedbackBasket projects with filtering options',
|
|
49
|
+
inputSchema: {
|
|
50
|
+
type: 'object',
|
|
51
|
+
properties: {
|
|
52
|
+
projectId: {
|
|
53
|
+
type: 'string',
|
|
54
|
+
description: 'Filter by specific project ID',
|
|
55
|
+
},
|
|
56
|
+
category: {
|
|
57
|
+
type: 'string',
|
|
58
|
+
enum: ['BUG', 'FEATURE', 'REVIEW'],
|
|
59
|
+
description: 'Filter by feedback category',
|
|
60
|
+
},
|
|
61
|
+
status: {
|
|
62
|
+
type: 'string',
|
|
63
|
+
enum: ['PENDING', 'REVIEWED', 'DONE'],
|
|
64
|
+
description: 'Filter by feedback status',
|
|
65
|
+
},
|
|
66
|
+
sentiment: {
|
|
67
|
+
type: 'string',
|
|
68
|
+
enum: ['POSITIVE', 'NEGATIVE', 'NEUTRAL'],
|
|
69
|
+
description: 'Filter by sentiment analysis result',
|
|
70
|
+
},
|
|
71
|
+
search: {
|
|
72
|
+
type: 'string',
|
|
73
|
+
description: 'Search feedback content for specific text',
|
|
74
|
+
},
|
|
75
|
+
limit: {
|
|
76
|
+
type: 'number',
|
|
77
|
+
description: 'Maximum number of results to return (default: 20, max: 100)',
|
|
78
|
+
minimum: 1,
|
|
79
|
+
maximum: 100,
|
|
80
|
+
},
|
|
81
|
+
includeNotes: {
|
|
82
|
+
type: 'boolean',
|
|
83
|
+
description: 'Include internal notes in the response (default: false)',
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
additionalProperties: false,
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
name: 'get_bug_reports',
|
|
91
|
+
description: 'Get bug reports specifically from your FeedbackBasket projects',
|
|
92
|
+
inputSchema: {
|
|
93
|
+
type: 'object',
|
|
94
|
+
properties: {
|
|
95
|
+
projectId: {
|
|
96
|
+
type: 'string',
|
|
97
|
+
description: 'Filter by specific project ID',
|
|
98
|
+
},
|
|
99
|
+
status: {
|
|
100
|
+
type: 'string',
|
|
101
|
+
enum: ['PENDING', 'REVIEWED', 'DONE'],
|
|
102
|
+
description: 'Filter by bug status',
|
|
103
|
+
},
|
|
104
|
+
severity: {
|
|
105
|
+
type: 'string',
|
|
106
|
+
enum: ['high', 'medium', 'low'],
|
|
107
|
+
description: 'Filter by computed severity (based on sentiment: negative=high, neutral=medium, positive=low)',
|
|
108
|
+
},
|
|
109
|
+
search: {
|
|
110
|
+
type: 'string',
|
|
111
|
+
description: 'Search bug report content for specific text',
|
|
112
|
+
},
|
|
113
|
+
limit: {
|
|
114
|
+
type: 'number',
|
|
115
|
+
description: 'Maximum number of results to return (default: 20, max: 100)',
|
|
116
|
+
minimum: 1,
|
|
117
|
+
maximum: 100,
|
|
118
|
+
},
|
|
119
|
+
includeNotes: {
|
|
120
|
+
type: 'boolean',
|
|
121
|
+
description: 'Include internal notes in the response (default: false)',
|
|
122
|
+
},
|
|
123
|
+
},
|
|
124
|
+
additionalProperties: false,
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
name: 'search_feedback',
|
|
129
|
+
description: 'Search for feedback across all accessible projects using text search',
|
|
130
|
+
inputSchema: {
|
|
131
|
+
type: 'object',
|
|
132
|
+
properties: {
|
|
133
|
+
query: {
|
|
134
|
+
type: 'string',
|
|
135
|
+
description: 'Search query to find in feedback content',
|
|
136
|
+
},
|
|
137
|
+
projectId: {
|
|
138
|
+
type: 'string',
|
|
139
|
+
description: 'Limit search to specific project',
|
|
140
|
+
},
|
|
141
|
+
category: {
|
|
142
|
+
type: 'string',
|
|
143
|
+
enum: ['BUG', 'FEATURE', 'REVIEW'],
|
|
144
|
+
description: 'Filter search results by category',
|
|
145
|
+
},
|
|
146
|
+
limit: {
|
|
147
|
+
type: 'number',
|
|
148
|
+
description: 'Maximum number of results (default: 10)',
|
|
149
|
+
minimum: 1,
|
|
150
|
+
maximum: 50,
|
|
151
|
+
},
|
|
152
|
+
},
|
|
153
|
+
required: ['query'],
|
|
154
|
+
additionalProperties: false,
|
|
155
|
+
},
|
|
156
|
+
},
|
|
157
|
+
],
|
|
158
|
+
};
|
|
159
|
+
});
|
|
160
|
+
// Handle tool calls
|
|
161
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
162
|
+
const { name, arguments: args } = request.params;
|
|
163
|
+
try {
|
|
164
|
+
switch (name) {
|
|
165
|
+
case 'list_projects':
|
|
166
|
+
return await client.listProjects();
|
|
167
|
+
case 'get_feedback':
|
|
168
|
+
return await client.getFeedback(args || {});
|
|
169
|
+
case 'get_bug_reports':
|
|
170
|
+
return await client.getBugReports(args || {});
|
|
171
|
+
case 'search_feedback':
|
|
172
|
+
if (!args || typeof args !== 'object' || !('query' in args) || typeof args.query !== 'string') {
|
|
173
|
+
throw new Error('Search query is required');
|
|
174
|
+
}
|
|
175
|
+
const searchOptions = {};
|
|
176
|
+
if (typeof args.projectId === 'string') {
|
|
177
|
+
searchOptions.projectId = args.projectId;
|
|
178
|
+
}
|
|
179
|
+
if (typeof args.category === 'string' && ['BUG', 'FEATURE', 'REVIEW'].includes(args.category)) {
|
|
180
|
+
searchOptions.category = args.category;
|
|
181
|
+
}
|
|
182
|
+
if (typeof args.limit === 'number') {
|
|
183
|
+
searchOptions.limit = args.limit;
|
|
184
|
+
}
|
|
185
|
+
return await client.searchFeedback(args.query, searchOptions);
|
|
186
|
+
default:
|
|
187
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
catch (error) {
|
|
191
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
|
|
192
|
+
return {
|
|
193
|
+
content: [
|
|
194
|
+
{
|
|
195
|
+
type: 'text',
|
|
196
|
+
text: `Error: ${errorMessage}`,
|
|
197
|
+
},
|
|
198
|
+
],
|
|
199
|
+
isError: true,
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
// Start the server
|
|
204
|
+
async function main() {
|
|
205
|
+
try {
|
|
206
|
+
const transport = new StdioServerTransport();
|
|
207
|
+
await server.connect(transport);
|
|
208
|
+
// Server is now running and will handle requests via stdio
|
|
209
|
+
console.error('FeedbackBasket MCP server started successfully');
|
|
210
|
+
}
|
|
211
|
+
catch (error) {
|
|
212
|
+
console.error('Failed to start MCP server:', error);
|
|
213
|
+
process.exit(1);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
// Handle graceful shutdown
|
|
217
|
+
process.on('SIGINT', () => {
|
|
218
|
+
console.error('Received SIGINT, shutting down gracefully...');
|
|
219
|
+
process.exit(0);
|
|
220
|
+
});
|
|
221
|
+
process.on('SIGTERM', () => {
|
|
222
|
+
console.error('Received SIGTERM, shutting down gracefully...');
|
|
223
|
+
process.exit(0);
|
|
224
|
+
});
|
|
225
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
226
|
+
main().catch((error) => {
|
|
227
|
+
console.error('Fatal error:', error);
|
|
228
|
+
process.exit(1);
|
|
229
|
+
});
|
|
230
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
export interface Project {
|
|
2
|
+
id: string;
|
|
3
|
+
name: string;
|
|
4
|
+
url: string;
|
|
5
|
+
description?: string;
|
|
6
|
+
createdAt: string;
|
|
7
|
+
updatedAt: string;
|
|
8
|
+
stats: {
|
|
9
|
+
totalFeedback: number;
|
|
10
|
+
byCategory: {
|
|
11
|
+
BUG: number;
|
|
12
|
+
FEATURE: number;
|
|
13
|
+
REVIEW: number;
|
|
14
|
+
UNKNOWN: number;
|
|
15
|
+
};
|
|
16
|
+
byStatus: {
|
|
17
|
+
PENDING: number;
|
|
18
|
+
REVIEWED: number;
|
|
19
|
+
DONE: number;
|
|
20
|
+
};
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
export interface Feedback {
|
|
24
|
+
id: string;
|
|
25
|
+
content: string;
|
|
26
|
+
email?: string;
|
|
27
|
+
status: 'PENDING' | 'REVIEWED' | 'DONE';
|
|
28
|
+
notes?: string;
|
|
29
|
+
category?: 'BUG' | 'FEATURE' | 'REVIEW';
|
|
30
|
+
categorySource: 'manual' | 'ai';
|
|
31
|
+
categoryConfidence?: number;
|
|
32
|
+
sentiment?: 'POSITIVE' | 'NEGATIVE' | 'NEUTRAL';
|
|
33
|
+
sentimentSource: 'manual' | 'ai';
|
|
34
|
+
sentimentConfidence?: number;
|
|
35
|
+
isAiAnalyzed: boolean;
|
|
36
|
+
aiAnalyzedAt?: string;
|
|
37
|
+
aiReasoning?: string;
|
|
38
|
+
project: {
|
|
39
|
+
id: string;
|
|
40
|
+
name: string;
|
|
41
|
+
url: string;
|
|
42
|
+
};
|
|
43
|
+
createdAt: string;
|
|
44
|
+
updatedAt: string;
|
|
45
|
+
}
|
|
46
|
+
export interface BugReport extends Feedback {
|
|
47
|
+
severity: 'high' | 'medium' | 'low';
|
|
48
|
+
}
|
|
49
|
+
export interface FeedbackBasketResponse<T> {
|
|
50
|
+
data?: T;
|
|
51
|
+
error?: {
|
|
52
|
+
message: string;
|
|
53
|
+
code?: string;
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
export interface ProjectsResponse {
|
|
57
|
+
projects: Project[];
|
|
58
|
+
totalProjects: number;
|
|
59
|
+
apiKeyInfo: {
|
|
60
|
+
name: string;
|
|
61
|
+
usageCount: number;
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
export interface FeedbackResponse {
|
|
65
|
+
feedback: Feedback[];
|
|
66
|
+
pagination: {
|
|
67
|
+
totalCount: number;
|
|
68
|
+
limit: number;
|
|
69
|
+
offset: number;
|
|
70
|
+
hasMore: boolean;
|
|
71
|
+
nextOffset: number | null;
|
|
72
|
+
};
|
|
73
|
+
filters: {
|
|
74
|
+
projectId?: string;
|
|
75
|
+
category?: string;
|
|
76
|
+
status?: string;
|
|
77
|
+
sentiment?: string;
|
|
78
|
+
search?: string;
|
|
79
|
+
};
|
|
80
|
+
apiKeyInfo: {
|
|
81
|
+
name: string;
|
|
82
|
+
usageCount: number;
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
export interface BugReportsResponse {
|
|
86
|
+
bugReports: BugReport[];
|
|
87
|
+
stats: {
|
|
88
|
+
totalBugs: number;
|
|
89
|
+
bySeverity: {
|
|
90
|
+
high: number;
|
|
91
|
+
medium: number;
|
|
92
|
+
low: number;
|
|
93
|
+
};
|
|
94
|
+
byStatus: {
|
|
95
|
+
pending: number;
|
|
96
|
+
reviewed: number;
|
|
97
|
+
done: number;
|
|
98
|
+
};
|
|
99
|
+
};
|
|
100
|
+
pagination: {
|
|
101
|
+
totalCount: number;
|
|
102
|
+
limit: number;
|
|
103
|
+
offset: number;
|
|
104
|
+
hasMore: boolean;
|
|
105
|
+
nextOffset: number | null;
|
|
106
|
+
};
|
|
107
|
+
filters: {
|
|
108
|
+
projectId?: string;
|
|
109
|
+
status?: string;
|
|
110
|
+
severity?: string;
|
|
111
|
+
search?: string;
|
|
112
|
+
};
|
|
113
|
+
apiKeyInfo: {
|
|
114
|
+
name: string;
|
|
115
|
+
usageCount: number;
|
|
116
|
+
};
|
|
117
|
+
}
|
package/dist/types.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "feedbackbasket-mcp-server",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "MCP server for FeedbackBasket project data access",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"feedbackbasket-mcp-server": "dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsc",
|
|
12
|
+
"start": "node dist/index.js",
|
|
13
|
+
"dev": "tsx src/index.ts",
|
|
14
|
+
"prepublishOnly": "npm run build"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"mcp",
|
|
18
|
+
"feedbackbasket",
|
|
19
|
+
"feedback",
|
|
20
|
+
"projects",
|
|
21
|
+
"ai-assistant",
|
|
22
|
+
"claude-desktop",
|
|
23
|
+
"cursor"
|
|
24
|
+
],
|
|
25
|
+
"author": "deifosv",
|
|
26
|
+
"license": "MIT",
|
|
27
|
+
"repository": {
|
|
28
|
+
"type": "git",
|
|
29
|
+
"url": "https://github.com/deifos/feedbackbasket-mcp.git"
|
|
30
|
+
},
|
|
31
|
+
"homepage": "https://feedbackbasket.com",
|
|
32
|
+
"bugs": {
|
|
33
|
+
"url": "https://github.com/deifos/feedbackbasket-mcp/issues"
|
|
34
|
+
},
|
|
35
|
+
"dependencies": {
|
|
36
|
+
"@modelcontextprotocol/sdk": "^0.4.0",
|
|
37
|
+
"axios": "^1.6.0",
|
|
38
|
+
"commander": "^11.0.0"
|
|
39
|
+
},
|
|
40
|
+
"devDependencies": {
|
|
41
|
+
"@types/node": "^20.0.0",
|
|
42
|
+
"typescript": "^5.0.0",
|
|
43
|
+
"tsx": "^4.0.0"
|
|
44
|
+
},
|
|
45
|
+
"files": [
|
|
46
|
+
"dist/**/*",
|
|
47
|
+
"README.md"
|
|
48
|
+
]
|
|
49
|
+
}
|