feedbackbasket-mcp-server 1.0.1 → 3.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/CHANGELOG.md +14 -0
- package/README.md +112 -113
- package/dist/client.d.ts +18 -56
- package/dist/client.js +75 -184
- package/dist/index.d.ts +21 -1
- package/dist/index.js +76 -227
- package/dist/types.d.ts +25 -69
- package/dist/types.js +1 -1
- package/package.json +54 -48
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## [3.0.0] - 2026-08-22
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- Added the complete 31-tool FeedbackBasket product-operation contract.
|
|
8
|
+
- Added structured MCP output, tool schemas, annotations, input checks, and confirmation checks.
|
|
9
|
+
- Added parity, protocol, client-path, build, and package release checks.
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
|
|
13
|
+
- Read and full keys now follow the same scopes and project restrictions as the live MCP server.
|
|
14
|
+
- The package, server, and shared contract now use version `3.0.0`.
|
package/README.md
CHANGED
|
@@ -1,93 +1,82 @@
|
|
|
1
1
|
# FeedbackBasket MCP Server
|
|
2
2
|
|
|
3
|
-
Model Context Protocol (MCP) server for FeedbackBasket
|
|
3
|
+
Model Context Protocol (MCP) server for [FeedbackBasket](https://feedbackbasket.com). Version `3.0.0` provides the same 31 product operations as the FeedbackBasket CLI and live Streamable HTTP server.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Use a read key for queries. Use a full key for approved writes. A project-restricted key can access only its allowed projects. Project creation and team operations need an unrestricted full key. Each high-impact operation needs `confirm: true`.
|
|
6
6
|
|
|
7
|
-
|
|
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
|
|
7
|
+
You can use this stdio package or connect directly to `https://feedbackbasket.com/.well-known/mcp` with Streamable HTTP. Both transports use the same MCP key and contract.
|
|
13
8
|
|
|
14
9
|
## Installation & Setup
|
|
15
10
|
|
|
16
11
|
### 1. Generate API Key
|
|
17
12
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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)
|
|
13
|
+
1. Log into [feedbackbasket.com](https://feedbackbasket.com)
|
|
14
|
+
2. Go to **Settings** (sidebar)
|
|
15
|
+
3. Scroll to **MCP API Keys** section
|
|
16
|
+
4. Click **New API Key**
|
|
17
|
+
5. Name your key (e.g., "Claude Code", "Cursor", "Windsurf")
|
|
18
|
+
6. Select projects to grant access to (or leave empty for all projects)
|
|
19
|
+
7. Copy the generated key. It is shown only once. Keep it out of source, logs, prompts, and command history.
|
|
29
20
|
|
|
30
21
|
### 2. Configure Your Editor
|
|
31
22
|
|
|
32
|
-
|
|
23
|
+
#### Claude Code (CLI)
|
|
33
24
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
"command": "npx",
|
|
40
|
-
"args": ["-y", "@feedbackbasket/mcp-server@latest", "--api-key", "fb_key_your_api_key_here"]
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
}
|
|
25
|
+
Set `FEEDBACKBASKET_API_KEY` in the environment that starts Claude Code. Then
|
|
26
|
+
add the server without putting the key in the command or shell history:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
claude mcp add feedbackbasket -- npx -y feedbackbasket-mcp-server@3.0.0
|
|
44
30
|
```
|
|
45
31
|
|
|
46
|
-
|
|
32
|
+
On native Windows, use `cmd /c npx` as the command because Claude Code cannot
|
|
33
|
+
start `npx` directly there.
|
|
47
34
|
|
|
48
35
|
#### Claude Desktop
|
|
36
|
+
|
|
37
|
+
Add to your Claude Desktop config (`claude_desktop_config.json`):
|
|
38
|
+
|
|
49
39
|
```json
|
|
50
40
|
{
|
|
51
41
|
"mcpServers": {
|
|
52
42
|
"feedbackbasket": {
|
|
53
43
|
"command": "npx",
|
|
54
|
-
"args": [
|
|
55
|
-
|
|
56
|
-
"@feedbackbasket/mcp-server@latest",
|
|
57
|
-
"--api-key",
|
|
58
|
-
"fb_key_your_api_key_here"
|
|
59
|
-
]
|
|
44
|
+
"args": ["-y", "feedbackbasket-mcp-server@3.0.0"],
|
|
45
|
+
"env": { "FEEDBACKBASKET_API_KEY": "${FEEDBACKBASKET_API_KEY}" }
|
|
60
46
|
}
|
|
61
47
|
}
|
|
62
48
|
}
|
|
63
49
|
```
|
|
64
50
|
|
|
65
|
-
#### Cursor/Windsurf
|
|
51
|
+
#### Cursor / Windsurf
|
|
52
|
+
|
|
53
|
+
Add to your MCP config (`.cursor/mcp.json` or equivalent):
|
|
54
|
+
|
|
66
55
|
```json
|
|
67
56
|
{
|
|
68
57
|
"mcpServers": {
|
|
69
58
|
"feedbackbasket": {
|
|
70
59
|
"command": "npx",
|
|
71
|
-
"args": [
|
|
72
|
-
|
|
73
|
-
"@feedbackbasket/mcp-server@latest",
|
|
74
|
-
"--api-key",
|
|
75
|
-
"fb_key_your_api_key_here"
|
|
76
|
-
]
|
|
60
|
+
"args": ["-y", "feedbackbasket-mcp-server@3.0.0"],
|
|
61
|
+
"env": { "FEEDBACKBASKET_API_KEY": "${FEEDBACKBASKET_API_KEY}" }
|
|
77
62
|
}
|
|
78
63
|
}
|
|
79
64
|
}
|
|
80
65
|
```
|
|
81
66
|
|
|
82
|
-
#### Environment Variable
|
|
67
|
+
#### Environment Variable
|
|
68
|
+
|
|
69
|
+
Use an environment variable instead of the `--api-key` argument. This keeps
|
|
70
|
+
the key out of process listings and saved command history.
|
|
71
|
+
|
|
83
72
|
```json
|
|
84
73
|
{
|
|
85
74
|
"mcpServers": {
|
|
86
75
|
"feedbackbasket": {
|
|
87
76
|
"command": "npx",
|
|
88
|
-
"args": ["-y", "
|
|
77
|
+
"args": ["-y", "feedbackbasket-mcp-server@latest"],
|
|
89
78
|
"env": {
|
|
90
|
-
"FEEDBACKBASKET_API_KEY": "
|
|
79
|
+
"FEEDBACKBASKET_API_KEY": "${FEEDBACKBASKET_API_KEY}"
|
|
91
80
|
}
|
|
92
81
|
}
|
|
93
82
|
}
|
|
@@ -96,97 +85,107 @@ Add the MCP server to your editor's configuration:
|
|
|
96
85
|
|
|
97
86
|
## Usage Examples
|
|
98
87
|
|
|
99
|
-
Once configured,
|
|
88
|
+
Once configured, ask your AI assistant:
|
|
100
89
|
|
|
101
90
|
### Project Overview
|
|
102
91
|
- "Show me all my FeedbackBasket projects"
|
|
103
|
-
- "
|
|
92
|
+
- "How much feedback does each project have?"
|
|
104
93
|
|
|
105
94
|
### Bug Reports
|
|
106
|
-
- "Show me all bug reports
|
|
107
|
-
- "Get high severity
|
|
108
|
-
- "Find
|
|
95
|
+
- "Show me all open bug reports"
|
|
96
|
+
- "Get high severity bugs that haven't been addressed"
|
|
97
|
+
- "Find bugs related to authentication"
|
|
109
98
|
|
|
110
99
|
### Feedback Analysis
|
|
111
|
-
- "Show me negative feedback from
|
|
112
|
-
- "Get all feature requests
|
|
113
|
-
- "
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
- "
|
|
118
|
-
- "
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
100
|
+
- "Show me negative feedback from my project"
|
|
101
|
+
- "Get all feature requests"
|
|
102
|
+
- "What are users asking for the most?"
|
|
103
|
+
- "Show me high priority feedback"
|
|
104
|
+
|
|
105
|
+
### Search
|
|
106
|
+
- "Search for feedback about 'payment issues'"
|
|
107
|
+
- "Find feedback mentioning 'mobile'"
|
|
108
|
+
|
|
109
|
+
### Agentic Workflows
|
|
110
|
+
- "Look at my bug reports and suggest which ones to fix first"
|
|
111
|
+
- "Summarize this week's feedback trends"
|
|
112
|
+
- "Are users happy with the new checkout flow?"
|
|
113
|
+
|
|
114
|
+
<!-- BEGIN GENERATED AGENT CAPABILITIES -->
|
|
115
|
+
## Agent capability contract
|
|
116
|
+
|
|
117
|
+
Agent surface version: `3.0.0`. The CLI and both MCP transports implement the same 31 product operations.
|
|
118
|
+
|
|
119
|
+
| Product operation | CLI command | MCP tool | Required access | Confirm |
|
|
120
|
+
| --- | --- | --- | --- | --- |
|
|
121
|
+
| `projects.list` | `projects list` | `list_projects` | `read:projects` | No |
|
|
122
|
+
| `projects.get` | `projects show` | `get_project` | `read:projects; allowed project` | No |
|
|
123
|
+
| `projects.create` | `projects create` | `create_project` | `write:projects; unrestricted key` | No |
|
|
124
|
+
| `projects.update` | `projects update` | `update_project` | `write:projects; allowed project` | No |
|
|
125
|
+
| `projects.delete` | `projects delete` | `delete_project` | `write:projects; allowed project` | Yes |
|
|
126
|
+
| `feedback.list` | `feedback list` | `get_feedback` | `read:feedback; allowed project` | No |
|
|
127
|
+
| `feedback.get` | `feedback show` | `get_feedback_item` | `read:feedback; allowed project` | No |
|
|
128
|
+
| `feedback.search` | `feedback search` | `search_feedback` | `read:feedback; allowed project` | No |
|
|
129
|
+
| `feedback.create` | `feedback create` | `create_feedback` | `write:feedback; allowed project` | No |
|
|
130
|
+
| `feedback.update` | `feedback update` | `update_feedback` | `write:feedback; allowed project` | No |
|
|
131
|
+
| `feedback.delete` | `feedback delete` | `delete_feedback` | `write:feedback; allowed project` | Yes |
|
|
132
|
+
| `feedback.bulkUpdate` | `feedback bulk-update` | `bulk_update_feedback` | `write:feedback; allowed project` | Yes |
|
|
133
|
+
| `feedback.export` | `feedback export` | `export_feedback` | `read:feedback; allowed project` | No |
|
|
134
|
+
| `bugs.list` | `bugs list` | `get_bug_reports` | `read:feedback; allowed project` | No |
|
|
135
|
+
| `bugs.stats` | `bugs stats` | `get_bug_stats` | `read:feedback; allowed project` | No |
|
|
136
|
+
| `notes.create` | `feedback note` | `create_feedback_note` | `write:notes; allowed project` | No |
|
|
137
|
+
| `notes.update` | `feedback note update` | `update_feedback_note` | `write:notes; allowed project` | No |
|
|
138
|
+
| `notes.delete` | `feedback note delete` | `delete_feedback_note` | `write:notes; allowed project` | Yes |
|
|
139
|
+
| `replies.list` | `feedback replies` | `list_feedback_replies` | `read:feedback; allowed project` | No |
|
|
140
|
+
| `replies.send` | `feedback reply` | `send_feedback_reply` | `write:replies; allowed project` | Yes |
|
|
141
|
+
| `widget.getSettings` | `widget settings` | `get_widget_settings` | `read:projects; allowed project` | No |
|
|
142
|
+
| `widget.updateSettings` | `widget update`<br>`widget flow` | `update_widget_settings` | `write:widget; allowed project` | No |
|
|
143
|
+
| `widget.getScript` | `widget script` | `get_widget_script` | `read:projects; allowed project` | No |
|
|
144
|
+
| `mobile.get` | `mobile status`<br>`mobile verify` | `get_mobile_integration` | `read:projects; allowed project` | No |
|
|
145
|
+
| `mobile.update` | `mobile setup`<br>`mobile bundle`<br>`mobile conversations`<br>`mobile disable` | `update_mobile_integration` | `write:mobile; allowed project` | No |
|
|
146
|
+
| `mobile.rotateKey` | `mobile rotate-key` | `rotate_mobile_project_key` | `write:mobile; allowed project` | Yes |
|
|
147
|
+
| `waitlist.list` | `waitlist list` | `get_waitlist` | `read:feedback; allowed project` | No |
|
|
148
|
+
| `waitlist.export` | `waitlist export` | `export_waitlist` | `read:feedback; allowed project` | No |
|
|
149
|
+
| `team.list` | `team list` | `list_team_members` | `write:team; unrestricted key` | No |
|
|
150
|
+
| `team.updateRole` | `team role` | `update_team_member_role` | `write:team; unrestricted key` | Yes |
|
|
151
|
+
| `team.remove` | `team remove` | `remove_team_member` | `write:team; unrestricted key` | Yes |
|
|
152
|
+
<!-- END GENERATED AGENT CAPABILITIES -->
|
|
150
153
|
|
|
151
154
|
## Security & Privacy
|
|
152
155
|
|
|
153
|
-
-
|
|
154
|
-
-
|
|
155
|
-
-
|
|
156
|
-
-
|
|
157
|
-
-
|
|
158
|
-
-
|
|
156
|
+
- **Explicit access** — Read keys query data. Full keys can use approved write operations.
|
|
157
|
+
- **Project-level permissions** — Restricted keys can access only selected projects.
|
|
158
|
+
- **Confirmation** — High-impact operations do not run without explicit confirmation.
|
|
159
|
+
- **API key authentication** — Secure token-based authentication
|
|
160
|
+
- **Usage tracking** — Monitor API key usage from your Settings page
|
|
161
|
+
- **Revokable access** — Deactivate or delete API keys instantly
|
|
159
162
|
|
|
160
163
|
## Troubleshooting
|
|
161
164
|
|
|
162
165
|
### "Invalid or missing API key"
|
|
163
166
|
- Check that your API key starts with `fb_key_`
|
|
164
|
-
- Ensure the
|
|
165
|
-
- Verify the
|
|
167
|
+
- Ensure the key is still active in Settings > MCP API Keys
|
|
168
|
+
- Verify the key has access to at least one project
|
|
166
169
|
|
|
167
170
|
### "No projects found"
|
|
168
171
|
- Make sure your API key has been granted access to projects
|
|
169
172
|
- Check that you have projects in your FeedbackBasket account
|
|
170
173
|
|
|
171
|
-
###
|
|
172
|
-
-
|
|
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
|
|
174
|
+
### Connection Issues
|
|
175
|
+
- Ensure Node.js 18+ is installed
|
|
177
176
|
- Try clearing npx cache: `npx clear-npx-cache`
|
|
178
|
-
-
|
|
177
|
+
- For local development, add `--base-url http://localhost:3000`
|
|
179
178
|
|
|
180
179
|
## API Key Management
|
|
181
180
|
|
|
182
|
-
Visit
|
|
181
|
+
Visit [feedbackbasket.com/dashboard/settings](https://feedbackbasket.com/dashboard/settings) to:
|
|
183
182
|
- Generate new API keys
|
|
184
|
-
- Manage project access
|
|
183
|
+
- Manage project access
|
|
185
184
|
- View usage statistics
|
|
186
|
-
-
|
|
187
|
-
-
|
|
185
|
+
- Activate/deactivate keys
|
|
186
|
+
- Delete keys
|
|
188
187
|
|
|
189
|
-
##
|
|
188
|
+
## Links
|
|
190
189
|
|
|
191
|
-
-
|
|
192
|
-
-
|
|
190
|
+
- [FeedbackBasket](https://feedbackbasket.com)
|
|
191
|
+
- [GitHub Issues](https://github.com/deifos/feedbackbasket-mcp/issues)
|
package/dist/client.d.ts
CHANGED
|
@@ -1,60 +1,22 @@
|
|
|
1
|
+
import { type ProductOperationId } from 'feedbackbasket-agent-contract';
|
|
2
|
+
export type McpOperationResult = {
|
|
3
|
+
structuredContent: Record<string, unknown>;
|
|
4
|
+
content: Array<{
|
|
5
|
+
type: 'text';
|
|
6
|
+
text: string;
|
|
7
|
+
}>;
|
|
8
|
+
isError?: boolean;
|
|
9
|
+
};
|
|
10
|
+
export type OperationRequest = {
|
|
11
|
+
method: "GET" | "POST" | "PATCH" | "DELETE";
|
|
12
|
+
url: string;
|
|
13
|
+
params?: Record<string, unknown>;
|
|
14
|
+
data?: Record<string, unknown>;
|
|
15
|
+
};
|
|
16
|
+
export declare function createOperationRequest(operationId: ProductOperationId, rawArgs: Record<string, unknown>): OperationRequest;
|
|
1
17
|
export declare class FeedbackBasketClient {
|
|
2
|
-
private api;
|
|
18
|
+
private readonly api;
|
|
3
19
|
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
|
-
}>;
|
|
20
|
+
execute(operationId: ProductOperationId, rawArgs: Record<string, unknown>): Promise<McpOperationResult>;
|
|
59
21
|
private handleError;
|
|
60
22
|
}
|
package/dist/client.js
CHANGED
|
@@ -1,207 +1,98 @@
|
|
|
1
1
|
import axios, { AxiosError } from 'axios';
|
|
2
|
+
import { AGENT_SURFACE_VERSION, getProductOperation, } from 'feedbackbasket-agent-contract';
|
|
3
|
+
export function createOperationRequest(operationId, rawArgs) {
|
|
4
|
+
const operation = getProductOperation(operationId);
|
|
5
|
+
if (!operation)
|
|
6
|
+
throw new Error(`Unknown operation: ${operationId}`);
|
|
7
|
+
const args = { ...rawArgs };
|
|
8
|
+
const requestPath = operation.http.path.replace(/\{([^}]+)\}/g, (_match, name) => {
|
|
9
|
+
const value = args[name];
|
|
10
|
+
if (typeof value !== 'string' || value.length === 0)
|
|
11
|
+
throw new Error(`${name} is required.`);
|
|
12
|
+
delete args[name];
|
|
13
|
+
return encodeURIComponent(value);
|
|
14
|
+
});
|
|
15
|
+
delete args.confirm;
|
|
16
|
+
if (operationId === 'feedback.search') {
|
|
17
|
+
args.search = args.query;
|
|
18
|
+
delete args.query;
|
|
19
|
+
}
|
|
20
|
+
if (operationId === 'widget.updateSettings' && args.settings && typeof args.settings === 'object') {
|
|
21
|
+
Object.assign(args, args.settings);
|
|
22
|
+
delete args.settings;
|
|
23
|
+
}
|
|
24
|
+
const params = {};
|
|
25
|
+
for (const name of operation.http.queryParameters) {
|
|
26
|
+
if (name in args) {
|
|
27
|
+
params[name] = args[name];
|
|
28
|
+
delete args[name];
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
if (operation.http.method === 'GET')
|
|
32
|
+
Object.assign(params, args);
|
|
33
|
+
return {
|
|
34
|
+
method: operation.http.method,
|
|
35
|
+
url: requestPath,
|
|
36
|
+
...(Object.keys(params).length > 0 ? { params } : {}),
|
|
37
|
+
...(operation.http.method === 'GET' ? {} : { data: args }),
|
|
38
|
+
};
|
|
39
|
+
}
|
|
2
40
|
export class FeedbackBasketClient {
|
|
3
41
|
api;
|
|
4
42
|
constructor(apiKey, baseUrl = 'https://feedbackbasket.com') {
|
|
5
43
|
this.api = axios.create({
|
|
6
|
-
baseURL:
|
|
44
|
+
baseURL: baseUrl.replace(/\/$/, ''),
|
|
7
45
|
headers: {
|
|
8
|
-
|
|
46
|
+
Authorization: `Bearer ${apiKey}`,
|
|
9
47
|
'Content-Type': 'application/json',
|
|
10
|
-
'User-Agent':
|
|
48
|
+
'User-Agent': `FeedbackBasket-MCP/${AGENT_SURFACE_VERSION}`,
|
|
11
49
|
},
|
|
12
|
-
timeout:
|
|
50
|
+
timeout: 30_000,
|
|
13
51
|
});
|
|
14
52
|
}
|
|
15
|
-
|
|
16
|
-
|
|
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 = {}) {
|
|
53
|
+
async execute(operationId, rawArgs) {
|
|
54
|
+
const request = createOperationRequest(operationId, rawArgs);
|
|
63
55
|
try {
|
|
64
|
-
const response = await this.api.
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
56
|
+
const response = await this.api.request({
|
|
57
|
+
...request,
|
|
58
|
+
transformResponse: [(value) => {
|
|
59
|
+
try {
|
|
60
|
+
return JSON.parse(value);
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
return value;
|
|
64
|
+
}
|
|
65
|
+
}],
|
|
68
66
|
});
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
|
|
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');
|
|
67
|
+
const structuredContent = response.data && typeof response.data === 'object' && !Array.isArray(response.data)
|
|
68
|
+
? response.data
|
|
69
|
+
: { value: response.data };
|
|
104
70
|
return {
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
text: summary
|
|
108
|
-
}]
|
|
71
|
+
structuredContent,
|
|
72
|
+
content: [{ type: 'text', text: JSON.stringify(structuredContent) }],
|
|
109
73
|
};
|
|
110
74
|
}
|
|
111
75
|
catch (error) {
|
|
112
|
-
throw this.handleError(
|
|
76
|
+
throw this.handleError(error);
|
|
113
77
|
}
|
|
114
78
|
}
|
|
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) {
|
|
79
|
+
handleError(error) {
|
|
189
80
|
if (error instanceof AxiosError) {
|
|
190
81
|
const status = error.response?.status;
|
|
191
|
-
const
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
return new Error(
|
|
203
|
-
}
|
|
82
|
+
const data = error.response?.data;
|
|
83
|
+
const responseMessage = typeof data?.error === 'string'
|
|
84
|
+
? data.error
|
|
85
|
+
: typeof data?.message === 'string'
|
|
86
|
+
? data.message
|
|
87
|
+
: error.message;
|
|
88
|
+
if (status === 401)
|
|
89
|
+
return new Error(`Authentication failed: ${responseMessage}.`);
|
|
90
|
+
if (status === 403)
|
|
91
|
+
return new Error(`Access denied: ${responseMessage}.`);
|
|
92
|
+
if (status === 429)
|
|
93
|
+
return new Error('Rate limit exceeded. Try again later.');
|
|
94
|
+
return new Error(`FeedbackBasket request failed: ${responseMessage} (HTTP ${status ?? 'unknown'}).`);
|
|
204
95
|
}
|
|
205
|
-
return new Error(
|
|
96
|
+
return new Error(error instanceof Error ? error.message : 'FeedbackBasket request failed.');
|
|
206
97
|
}
|
|
207
98
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,2 +1,22 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
2
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
3
|
+
import { FeedbackBasketClient, type McpOperationResult } from './client.js';
|
|
4
|
+
export declare const MCP_TOOLS: {
|
|
5
|
+
name: string;
|
|
6
|
+
title: string;
|
|
7
|
+
description: string;
|
|
8
|
+
inputSchema: Readonly<Record<string, unknown>>;
|
|
9
|
+
outputSchema: Readonly<Record<string, unknown>>;
|
|
10
|
+
annotations: {
|
|
11
|
+
readOnlyHint: boolean;
|
|
12
|
+
destructiveHint: boolean;
|
|
13
|
+
idempotentHint: boolean;
|
|
14
|
+
openWorldHint: boolean;
|
|
15
|
+
};
|
|
16
|
+
}[];
|
|
17
|
+
export declare function parseArgs(args: string[]): {
|
|
18
|
+
apiKey?: string;
|
|
19
|
+
baseUrl?: string;
|
|
20
|
+
};
|
|
21
|
+
export declare function dispatchTool(client: Pick<FeedbackBasketClient, 'execute'>, name: string, args: unknown): Promise<McpOperationResult>;
|
|
22
|
+
export declare function createServer(client: FeedbackBasketClient): Server;
|
package/dist/index.js
CHANGED
|
@@ -1,241 +1,90 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
2
4
|
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
3
5
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
4
6
|
import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
|
|
7
|
+
import { AGENT_SURFACE_VERSION, PRODUCT_OPERATIONS, getProductOperationByTool, validateOperationInput, } from 'feedbackbasket-agent-contract';
|
|
5
8
|
import { FeedbackBasketClient } from './client.js';
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
9
|
+
export const MCP_TOOLS = PRODUCT_OPERATIONS.map((operation) => ({
|
|
10
|
+
name: operation.mcp.name,
|
|
11
|
+
title: operation.mcp.title,
|
|
12
|
+
description: operation.mcp.description,
|
|
13
|
+
inputSchema: operation.mcp.inputSchema,
|
|
14
|
+
outputSchema: operation.mcp.outputSchema,
|
|
15
|
+
annotations: {
|
|
16
|
+
readOnlyHint: operation.risk.readOnly,
|
|
17
|
+
destructiveHint: operation.risk.destructive,
|
|
18
|
+
idempotentHint: operation.risk.idempotent,
|
|
19
|
+
openWorldHint: operation.risk.openWorld,
|
|
20
|
+
},
|
|
21
|
+
}));
|
|
22
|
+
export function parseArgs(args) {
|
|
23
|
+
const result = {};
|
|
24
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
25
|
+
const arg = args[index] ?? '';
|
|
26
|
+
if (arg === '--api-key') {
|
|
27
|
+
const value = args[++index];
|
|
28
|
+
if (value !== undefined)
|
|
29
|
+
result.apiKey = value;
|
|
18
30
|
}
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
// Validate API key format
|
|
31
|
-
if (!apiKey.startsWith('fb_key_')) {
|
|
32
|
-
console.error('Error: Invalid API key format. API keys should start with "fb_key_".');
|
|
33
|
-
process.exit(1);
|
|
31
|
+
else if (arg.startsWith('--api-key='))
|
|
32
|
+
result.apiKey = arg.slice('--api-key='.length);
|
|
33
|
+
else if (arg === '--base-url') {
|
|
34
|
+
const value = args[++index];
|
|
35
|
+
if (value !== undefined)
|
|
36
|
+
result.baseUrl = value;
|
|
37
|
+
}
|
|
38
|
+
else if (arg.startsWith('--base-url='))
|
|
39
|
+
result.baseUrl = arg.slice('--base-url='.length);
|
|
40
|
+
}
|
|
41
|
+
return result;
|
|
34
42
|
}
|
|
35
|
-
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
// List available tools
|
|
46
|
-
server.setRequestHandler(ListToolsRequestSchema, async (_request) => {
|
|
47
|
-
return {
|
|
48
|
-
tools: [
|
|
49
|
-
{
|
|
50
|
-
name: 'list_projects',
|
|
51
|
-
description: 'List all FeedbackBasket projects accessible by your API key with summary statistics',
|
|
52
|
-
inputSchema: {
|
|
53
|
-
type: 'object',
|
|
54
|
-
properties: {},
|
|
55
|
-
additionalProperties: false,
|
|
56
|
-
},
|
|
57
|
-
},
|
|
58
|
-
{
|
|
59
|
-
name: 'get_feedback',
|
|
60
|
-
description: 'Get feedback from your FeedbackBasket projects with filtering options',
|
|
61
|
-
inputSchema: {
|
|
62
|
-
type: 'object',
|
|
63
|
-
properties: {
|
|
64
|
-
projectId: {
|
|
65
|
-
type: 'string',
|
|
66
|
-
description: 'Filter by specific project ID',
|
|
67
|
-
},
|
|
68
|
-
category: {
|
|
69
|
-
type: 'string',
|
|
70
|
-
enum: ['BUG', 'FEATURE', 'REVIEW'],
|
|
71
|
-
description: 'Filter by feedback category',
|
|
72
|
-
},
|
|
73
|
-
status: {
|
|
74
|
-
type: 'string',
|
|
75
|
-
enum: ['PENDING', 'REVIEWED', 'DONE'],
|
|
76
|
-
description: 'Filter by feedback status',
|
|
77
|
-
},
|
|
78
|
-
sentiment: {
|
|
79
|
-
type: 'string',
|
|
80
|
-
enum: ['POSITIVE', 'NEGATIVE', 'NEUTRAL'],
|
|
81
|
-
description: 'Filter by sentiment analysis result',
|
|
82
|
-
},
|
|
83
|
-
search: {
|
|
84
|
-
type: 'string',
|
|
85
|
-
description: 'Search feedback content for specific text',
|
|
86
|
-
},
|
|
87
|
-
limit: {
|
|
88
|
-
type: 'number',
|
|
89
|
-
description: 'Maximum number of results to return (default: 20, max: 100)',
|
|
90
|
-
minimum: 1,
|
|
91
|
-
maximum: 100,
|
|
92
|
-
},
|
|
93
|
-
includeNotes: {
|
|
94
|
-
type: 'boolean',
|
|
95
|
-
description: 'Include internal notes in the response (default: false)',
|
|
96
|
-
},
|
|
97
|
-
},
|
|
98
|
-
additionalProperties: false,
|
|
99
|
-
},
|
|
100
|
-
},
|
|
101
|
-
{
|
|
102
|
-
name: 'get_bug_reports',
|
|
103
|
-
description: 'Get bug reports specifically from your FeedbackBasket projects',
|
|
104
|
-
inputSchema: {
|
|
105
|
-
type: 'object',
|
|
106
|
-
properties: {
|
|
107
|
-
projectId: {
|
|
108
|
-
type: 'string',
|
|
109
|
-
description: 'Filter by specific project ID',
|
|
110
|
-
},
|
|
111
|
-
status: {
|
|
112
|
-
type: 'string',
|
|
113
|
-
enum: ['PENDING', 'REVIEWED', 'DONE'],
|
|
114
|
-
description: 'Filter by bug status',
|
|
115
|
-
},
|
|
116
|
-
severity: {
|
|
117
|
-
type: 'string',
|
|
118
|
-
enum: ['high', 'medium', 'low'],
|
|
119
|
-
description: 'Filter by computed severity (based on sentiment: negative=high, neutral=medium, positive=low)',
|
|
120
|
-
},
|
|
121
|
-
search: {
|
|
122
|
-
type: 'string',
|
|
123
|
-
description: 'Search bug report content for specific text',
|
|
124
|
-
},
|
|
125
|
-
limit: {
|
|
126
|
-
type: 'number',
|
|
127
|
-
description: 'Maximum number of results to return (default: 20, max: 100)',
|
|
128
|
-
minimum: 1,
|
|
129
|
-
maximum: 100,
|
|
130
|
-
},
|
|
131
|
-
includeNotes: {
|
|
132
|
-
type: 'boolean',
|
|
133
|
-
description: 'Include internal notes in the response (default: false)',
|
|
134
|
-
},
|
|
135
|
-
},
|
|
136
|
-
additionalProperties: false,
|
|
137
|
-
},
|
|
138
|
-
},
|
|
139
|
-
{
|
|
140
|
-
name: 'search_feedback',
|
|
141
|
-
description: 'Search for feedback across all accessible projects using text search',
|
|
142
|
-
inputSchema: {
|
|
143
|
-
type: 'object',
|
|
144
|
-
properties: {
|
|
145
|
-
query: {
|
|
146
|
-
type: 'string',
|
|
147
|
-
description: 'Search query to find in feedback content',
|
|
148
|
-
},
|
|
149
|
-
projectId: {
|
|
150
|
-
type: 'string',
|
|
151
|
-
description: 'Limit search to specific project',
|
|
152
|
-
},
|
|
153
|
-
category: {
|
|
154
|
-
type: 'string',
|
|
155
|
-
enum: ['BUG', 'FEATURE', 'REVIEW'],
|
|
156
|
-
description: 'Filter search results by category',
|
|
157
|
-
},
|
|
158
|
-
limit: {
|
|
159
|
-
type: 'number',
|
|
160
|
-
description: 'Maximum number of results (default: 10)',
|
|
161
|
-
minimum: 1,
|
|
162
|
-
maximum: 50,
|
|
163
|
-
},
|
|
164
|
-
},
|
|
165
|
-
required: ['query'],
|
|
166
|
-
additionalProperties: false,
|
|
167
|
-
},
|
|
168
|
-
},
|
|
169
|
-
],
|
|
170
|
-
};
|
|
171
|
-
});
|
|
172
|
-
// Handle tool calls
|
|
173
|
-
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
174
|
-
const { name, arguments: args } = request.params;
|
|
43
|
+
export async function dispatchTool(client, name, args) {
|
|
44
|
+
const operation = getProductOperationByTool(name);
|
|
45
|
+
if (!operation)
|
|
46
|
+
return errorResult(`Unknown tool: ${name}`);
|
|
47
|
+
const input = args && typeof args === 'object' && !Array.isArray(args)
|
|
48
|
+
? args
|
|
49
|
+
: {};
|
|
50
|
+
const validation = validateOperationInput(operation, input);
|
|
51
|
+
if (!validation.valid)
|
|
52
|
+
return errorResult(validation.message);
|
|
175
53
|
try {
|
|
176
|
-
|
|
177
|
-
case 'list_projects':
|
|
178
|
-
return await client.listProjects();
|
|
179
|
-
case 'get_feedback':
|
|
180
|
-
return await client.getFeedback(args || {});
|
|
181
|
-
case 'get_bug_reports':
|
|
182
|
-
return await client.getBugReports(args || {});
|
|
183
|
-
case 'search_feedback':
|
|
184
|
-
if (!args || typeof args !== 'object' || !('query' in args) || typeof args.query !== 'string') {
|
|
185
|
-
throw new Error('Search query is required');
|
|
186
|
-
}
|
|
187
|
-
const searchOptions = {};
|
|
188
|
-
if (typeof args.projectId === 'string') {
|
|
189
|
-
searchOptions.projectId = args.projectId;
|
|
190
|
-
}
|
|
191
|
-
if (typeof args.category === 'string' && ['BUG', 'FEATURE', 'REVIEW'].includes(args.category)) {
|
|
192
|
-
searchOptions.category = args.category;
|
|
193
|
-
}
|
|
194
|
-
if (typeof args.limit === 'number') {
|
|
195
|
-
searchOptions.limit = args.limit;
|
|
196
|
-
}
|
|
197
|
-
return await client.searchFeedback(args.query, searchOptions);
|
|
198
|
-
default:
|
|
199
|
-
throw new Error(`Unknown tool: ${name}`);
|
|
200
|
-
}
|
|
54
|
+
return await client.execute(operation.id, input);
|
|
201
55
|
}
|
|
202
56
|
catch (error) {
|
|
203
|
-
|
|
204
|
-
return {
|
|
205
|
-
content: [
|
|
206
|
-
{
|
|
207
|
-
type: 'text',
|
|
208
|
-
text: `Error: ${errorMessage}`,
|
|
209
|
-
},
|
|
210
|
-
],
|
|
211
|
-
isError: true,
|
|
212
|
-
};
|
|
57
|
+
return errorResult(error instanceof Error ? error.message : 'FeedbackBasket request failed.');
|
|
213
58
|
}
|
|
214
|
-
}
|
|
215
|
-
|
|
59
|
+
}
|
|
60
|
+
function errorResult(message) {
|
|
61
|
+
const structuredContent = { error: message };
|
|
62
|
+
return {
|
|
63
|
+
structuredContent,
|
|
64
|
+
content: [{ type: 'text', text: JSON.stringify(structuredContent) }],
|
|
65
|
+
isError: true,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
export function createServer(client) {
|
|
69
|
+
const server = new Server({ name: 'feedbackbasket-mcp', version: AGENT_SURFACE_VERSION }, { capabilities: { tools: {} } });
|
|
70
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: MCP_TOOLS }));
|
|
71
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => dispatchTool(client, request.params.name, request.params.arguments));
|
|
72
|
+
return server;
|
|
73
|
+
}
|
|
216
74
|
async function main() {
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
75
|
+
const options = parseArgs(process.argv.slice(2));
|
|
76
|
+
const apiKey = options.apiKey || process.env.FEEDBACKBASKET_API_KEY;
|
|
77
|
+
if (!apiKey)
|
|
78
|
+
throw new Error('API key required. Use --api-key or FEEDBACKBASKET_API_KEY.');
|
|
79
|
+
if (!/^fb_key_[a-f0-9]{64}$/.test(apiKey))
|
|
80
|
+
throw new Error('Invalid API key format.');
|
|
81
|
+
const server = createServer(new FeedbackBasketClient(apiKey, options.baseUrl));
|
|
82
|
+
await server.connect(new StdioServerTransport());
|
|
83
|
+
console.error(`FeedbackBasket MCP server v${AGENT_SURFACE_VERSION} started`);
|
|
84
|
+
}
|
|
85
|
+
if (process.argv[1] && fileURLToPath(import.meta.url) === path.resolve(process.argv[1])) {
|
|
86
|
+
main().catch((error) => {
|
|
87
|
+
console.error(error instanceof Error ? error.message : 'MCP server failed.');
|
|
225
88
|
process.exit(1);
|
|
226
|
-
}
|
|
89
|
+
});
|
|
227
90
|
}
|
|
228
|
-
// Handle graceful shutdown
|
|
229
|
-
process.on('SIGINT', () => {
|
|
230
|
-
console.error('Received SIGINT, shutting down gracefully...');
|
|
231
|
-
process.exit(0);
|
|
232
|
-
});
|
|
233
|
-
process.on('SIGTERM', () => {
|
|
234
|
-
console.error('Received SIGTERM, shutting down gracefully...');
|
|
235
|
-
process.exit(0);
|
|
236
|
-
});
|
|
237
|
-
// Only run if this file is the main module
|
|
238
|
-
main().catch((error) => {
|
|
239
|
-
console.error('🚨 Fatal error:', error);
|
|
240
|
-
process.exit(1);
|
|
241
|
-
});
|
package/dist/types.d.ts
CHANGED
|
@@ -4,62 +4,45 @@ export interface Project {
|
|
|
4
4
|
url: string;
|
|
5
5
|
description?: string;
|
|
6
6
|
createdAt: string;
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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
|
-
};
|
|
7
|
+
totalFeedback: number;
|
|
8
|
+
byStatus: Record<string, number>;
|
|
9
|
+
byCategory: Record<string, number>;
|
|
22
10
|
}
|
|
23
11
|
export interface Feedback {
|
|
24
12
|
id: string;
|
|
25
13
|
content: string;
|
|
26
|
-
email?: string;
|
|
27
|
-
status: '
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
14
|
+
email?: string | null;
|
|
15
|
+
status: 'OPEN' | 'UNDER_REVIEW' | 'PLANNED' | 'IN_PROGRESS' | 'COMPLETE' | 'CLOSED';
|
|
16
|
+
category?: 'BUG' | 'FEATURE_REQUEST' | 'IMPROVEMENT' | 'QUESTION' | null;
|
|
17
|
+
sentiment?: 'POSITIVE' | 'NEGATIVE' | 'NEUTRAL' | null;
|
|
18
|
+
aiSummary?: string | null;
|
|
19
|
+
aiPriorityScore?: number | null;
|
|
20
|
+
reasoning?: string | null;
|
|
21
|
+
pageUrl?: string | null;
|
|
22
|
+
browser?: string | null;
|
|
23
|
+
os?: string | null;
|
|
24
|
+
device?: string | null;
|
|
25
|
+
language?: string | null;
|
|
38
26
|
project: {
|
|
39
27
|
id: string;
|
|
40
28
|
name: string;
|
|
41
|
-
url: string;
|
|
42
29
|
};
|
|
30
|
+
notes?: Array<{
|
|
31
|
+
id: string;
|
|
32
|
+
content: string;
|
|
33
|
+
createdAt: string;
|
|
34
|
+
author: {
|
|
35
|
+
name: string;
|
|
36
|
+
};
|
|
37
|
+
}>;
|
|
43
38
|
createdAt: string;
|
|
44
|
-
updatedAt: string;
|
|
45
39
|
}
|
|
46
40
|
export interface BugReport extends Feedback {
|
|
47
41
|
severity: 'high' | 'medium' | 'low';
|
|
48
42
|
}
|
|
49
|
-
export interface FeedbackBasketResponse<T> {
|
|
50
|
-
data?: T;
|
|
51
|
-
error?: {
|
|
52
|
-
message: string;
|
|
53
|
-
code?: string;
|
|
54
|
-
};
|
|
55
|
-
}
|
|
56
43
|
export interface ProjectsResponse {
|
|
57
44
|
projects: Project[];
|
|
58
45
|
totalProjects: number;
|
|
59
|
-
apiKeyInfo: {
|
|
60
|
-
name: string;
|
|
61
|
-
usageCount: number;
|
|
62
|
-
};
|
|
63
46
|
}
|
|
64
47
|
export interface FeedbackResponse {
|
|
65
48
|
feedback: Feedback[];
|
|
@@ -68,50 +51,23 @@ export interface FeedbackResponse {
|
|
|
68
51
|
limit: number;
|
|
69
52
|
offset: number;
|
|
70
53
|
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
54
|
};
|
|
84
55
|
}
|
|
85
56
|
export interface BugReportsResponse {
|
|
86
57
|
bugReports: BugReport[];
|
|
87
58
|
stats: {
|
|
88
|
-
|
|
59
|
+
total: number;
|
|
89
60
|
bySeverity: {
|
|
90
61
|
high: number;
|
|
91
62
|
medium: number;
|
|
92
63
|
low: number;
|
|
93
64
|
};
|
|
94
|
-
byStatus:
|
|
95
|
-
pending: number;
|
|
96
|
-
reviewed: number;
|
|
97
|
-
done: number;
|
|
98
|
-
};
|
|
65
|
+
byStatus: Record<string, number>;
|
|
99
66
|
};
|
|
100
67
|
pagination: {
|
|
101
68
|
totalCount: number;
|
|
102
69
|
limit: number;
|
|
103
70
|
offset: number;
|
|
104
71
|
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
72
|
};
|
|
117
73
|
}
|
package/dist/types.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
// Shared types for the MCP server
|
|
1
|
+
// Shared types for the MCP server — aligned with FeedbackBasket v3
|
|
2
2
|
export {};
|
package/package.json
CHANGED
|
@@ -1,48 +1,54 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "feedbackbasket-mcp-server",
|
|
3
|
-
"version": "
|
|
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
|
-
"
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
"
|
|
18
|
-
"
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
"
|
|
22
|
-
"
|
|
23
|
-
"
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
"
|
|
32
|
-
|
|
33
|
-
"url": "https://github.com/deifos/feedbackbasket-mcp
|
|
34
|
-
},
|
|
35
|
-
"
|
|
36
|
-
|
|
37
|
-
"
|
|
38
|
-
},
|
|
39
|
-
"
|
|
40
|
-
"@
|
|
41
|
-
"
|
|
42
|
-
"
|
|
43
|
-
},
|
|
44
|
-
"
|
|
45
|
-
"
|
|
46
|
-
"
|
|
47
|
-
|
|
48
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "feedbackbasket-mcp-server",
|
|
3
|
+
"version": "3.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": "node node_modules/typescript/bin/tsc",
|
|
12
|
+
"start": "node dist/index.js",
|
|
13
|
+
"dev": "tsx src/index.ts",
|
|
14
|
+
"test": "tsx --test tests/*.test.ts",
|
|
15
|
+
"check:parity": "tsx scripts/check-parity.ts",
|
|
16
|
+
"docs:generate": "tsx scripts/generate-capabilities.ts",
|
|
17
|
+
"docs:check": "tsx scripts/generate-capabilities.ts --check",
|
|
18
|
+
"prepublishOnly": "npm run check:parity && npm run docs:check && npm test && npm run build"
|
|
19
|
+
},
|
|
20
|
+
"keywords": [
|
|
21
|
+
"mcp",
|
|
22
|
+
"feedbackbasket",
|
|
23
|
+
"feedback",
|
|
24
|
+
"projects",
|
|
25
|
+
"ai-assistant",
|
|
26
|
+
"claude-desktop",
|
|
27
|
+
"cursor"
|
|
28
|
+
],
|
|
29
|
+
"author": "deifosv",
|
|
30
|
+
"license": "MIT",
|
|
31
|
+
"repository": {
|
|
32
|
+
"type": "git",
|
|
33
|
+
"url": "https://github.com/deifos/feedbackbasket-mcp.git"
|
|
34
|
+
},
|
|
35
|
+
"homepage": "https://feedbackbasket.com",
|
|
36
|
+
"bugs": {
|
|
37
|
+
"url": "https://github.com/deifos/feedbackbasket-mcp/issues"
|
|
38
|
+
},
|
|
39
|
+
"dependencies": {
|
|
40
|
+
"@modelcontextprotocol/sdk": "1.30.0",
|
|
41
|
+
"axios": "^1.19.0",
|
|
42
|
+
"feedbackbasket-agent-contract": "3.0.0"
|
|
43
|
+
},
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"@types/node": "^20.0.0",
|
|
46
|
+
"tsx": "^4.0.0",
|
|
47
|
+
"typescript": "^5.0.0"
|
|
48
|
+
},
|
|
49
|
+
"files": [
|
|
50
|
+
"dist/**/*",
|
|
51
|
+
"README.md",
|
|
52
|
+
"CHANGELOG.md"
|
|
53
|
+
]
|
|
54
|
+
}
|