feedbackbasket-mcp-server 2.0.0 → 3.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/CHANGELOG.md +25 -0
- package/README.md +202 -192
- package/dist/client.d.ts +18 -46
- package/dist/client.js +75 -185
- package/dist/index.d.ts +21 -1
- package/dist/index.js +74 -212
- package/package.json +54 -48
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## [3.1.0] - 2026-08-22
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- Documented browser OAuth as the recommended remote Streamable HTTP setup.
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
|
|
11
|
+
- The STDIO package remains API-key based for local, CI, server, and unattended use.
|
|
12
|
+
- The package and shared contract now report version `3.1.0` with the same 31 operations.
|
|
13
|
+
|
|
14
|
+
## [3.0.0] - 2026-08-22
|
|
15
|
+
|
|
16
|
+
### Added
|
|
17
|
+
|
|
18
|
+
- Added the complete 31-tool FeedbackBasket product-operation contract.
|
|
19
|
+
- Added structured MCP output, tool schemas, annotations, input checks, and confirmation checks.
|
|
20
|
+
- Added parity, protocol, client-path, build, and package release checks.
|
|
21
|
+
|
|
22
|
+
### Changed
|
|
23
|
+
|
|
24
|
+
- Read and full keys now follow the same scopes and project restrictions as the live MCP server.
|
|
25
|
+
- The package, server, and shared contract now use version `3.0.0`.
|
package/README.md
CHANGED
|
@@ -1,192 +1,202 @@
|
|
|
1
|
-
# FeedbackBasket MCP Server
|
|
2
|
-
|
|
3
|
-
Model Context Protocol (MCP) server for FeedbackBasket
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
- "
|
|
107
|
-
- "
|
|
108
|
-
- "
|
|
109
|
-
|
|
110
|
-
###
|
|
111
|
-
|
|
112
|
-
- "
|
|
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
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
-
|
|
164
|
-
-
|
|
165
|
-
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
-
|
|
186
|
-
-
|
|
187
|
-
-
|
|
188
|
-
|
|
189
|
-
##
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
1
|
+
# FeedbackBasket MCP Server
|
|
2
|
+
|
|
3
|
+
Model Context Protocol (MCP) server for [FeedbackBasket](https://feedbackbasket.com). Version `3.1.0` provides the same 31 product operations as the FeedbackBasket CLI and live Streamable HTTP server.
|
|
4
|
+
|
|
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
|
+
|
|
7
|
+
Use this STDIO package with an MCP key for local processes, CI, servers, and unattended automation. For remote Streamable HTTP, add `https://feedbackbasket.com/.well-known/mcp` to the host, save it, and select **Authenticate**. Sign in, select an organization, select Read or Full access, select Selected projects or All projects, and select **Allow**. Browser OAuth is the recommended remote setup. It is not used by STDIO.
|
|
8
|
+
|
|
9
|
+
Read is selected by default. Full needs an explicit choice and an owner or administrator role. Selected projects limits access to the approved projects. All projects includes current and future projects and is required for project creation and team operations. Existing unrestricted MCP keys remain compatible. New browser grants never receive All-projects access silently. Never put an access token, refresh token, MCP key, or CLI token in source, prompts, logs, generated configuration, or final output.
|
|
10
|
+
|
|
11
|
+
## Installation & Setup
|
|
12
|
+
|
|
13
|
+
### 1. Generate API Key
|
|
14
|
+
|
|
15
|
+
1. Log into [feedbackbasket.com](https://feedbackbasket.com)
|
|
16
|
+
2. Go to **Settings** (sidebar)
|
|
17
|
+
3. Scroll to **MCP API Keys** section
|
|
18
|
+
4. Click **New API Key**
|
|
19
|
+
5. Name your key (e.g., "Claude Code", "Cursor", "Windsurf")
|
|
20
|
+
6. Select projects to grant access to (or leave empty for all projects)
|
|
21
|
+
7. Copy the generated key. It is shown only once. Keep it out of source, logs, prompts, and command history.
|
|
22
|
+
|
|
23
|
+
### 2. Configure Your Editor
|
|
24
|
+
|
|
25
|
+
#### Claude Code (CLI)
|
|
26
|
+
|
|
27
|
+
Set `FEEDBACKBASKET_API_KEY` in the environment that starts Claude Code. Then
|
|
28
|
+
add the server without putting the key in the command or shell history:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
claude mcp add feedbackbasket -- npx -y feedbackbasket-mcp-server@3.1.0
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
On native Windows, use `cmd /c npx` as the command because Claude Code cannot
|
|
35
|
+
start `npx` directly there.
|
|
36
|
+
|
|
37
|
+
#### Claude Desktop
|
|
38
|
+
|
|
39
|
+
Add to your Claude Desktop config (`claude_desktop_config.json`):
|
|
40
|
+
|
|
41
|
+
```json
|
|
42
|
+
{
|
|
43
|
+
"mcpServers": {
|
|
44
|
+
"feedbackbasket": {
|
|
45
|
+
"command": "npx",
|
|
46
|
+
"args": ["-y", "feedbackbasket-mcp-server@3.1.0"],
|
|
47
|
+
"env": { "FEEDBACKBASKET_API_KEY": "${FEEDBACKBASKET_API_KEY}" }
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
#### Cursor / Windsurf
|
|
54
|
+
|
|
55
|
+
Add to your MCP config (`.cursor/mcp.json` or equivalent):
|
|
56
|
+
|
|
57
|
+
```json
|
|
58
|
+
{
|
|
59
|
+
"mcpServers": {
|
|
60
|
+
"feedbackbasket": {
|
|
61
|
+
"command": "npx",
|
|
62
|
+
"args": ["-y", "feedbackbasket-mcp-server@3.1.0"],
|
|
63
|
+
"env": { "FEEDBACKBASKET_API_KEY": "${FEEDBACKBASKET_API_KEY}" }
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
#### Environment Variable
|
|
70
|
+
|
|
71
|
+
Use an environment variable instead of the `--api-key` argument. This keeps
|
|
72
|
+
the key out of process listings and saved command history.
|
|
73
|
+
|
|
74
|
+
```json
|
|
75
|
+
{
|
|
76
|
+
"mcpServers": {
|
|
77
|
+
"feedbackbasket": {
|
|
78
|
+
"command": "npx",
|
|
79
|
+
"args": ["-y", "feedbackbasket-mcp-server@3.1.0"],
|
|
80
|
+
"env": {
|
|
81
|
+
"FEEDBACKBASKET_API_KEY": "${FEEDBACKBASKET_API_KEY}"
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Usage Examples
|
|
89
|
+
|
|
90
|
+
Once configured, ask your AI assistant:
|
|
91
|
+
|
|
92
|
+
### Project Overview
|
|
93
|
+
|
|
94
|
+
- "Show me all my FeedbackBasket projects"
|
|
95
|
+
- "How much feedback does each project have?"
|
|
96
|
+
|
|
97
|
+
### Bug Reports
|
|
98
|
+
|
|
99
|
+
- "Show me all open bug reports"
|
|
100
|
+
- "Get high severity bugs that haven't been addressed"
|
|
101
|
+
- "Find bugs related to authentication"
|
|
102
|
+
|
|
103
|
+
### Feedback Analysis
|
|
104
|
+
|
|
105
|
+
- "Show me negative feedback from my project"
|
|
106
|
+
- "Get all feature requests"
|
|
107
|
+
- "What are users asking for the most?"
|
|
108
|
+
- "Show me high priority feedback"
|
|
109
|
+
|
|
110
|
+
### Search
|
|
111
|
+
|
|
112
|
+
- "Search for feedback about 'payment issues'"
|
|
113
|
+
- "Find feedback mentioning 'mobile'"
|
|
114
|
+
|
|
115
|
+
### Agentic Workflows
|
|
116
|
+
|
|
117
|
+
- "Look at my bug reports and suggest which ones to fix first"
|
|
118
|
+
- "Summarize this week's feedback trends"
|
|
119
|
+
- "Are users happy with the new checkout flow?"
|
|
120
|
+
|
|
121
|
+
<!-- BEGIN GENERATED AGENT CAPABILITIES -->
|
|
122
|
+
## Agent capability contract
|
|
123
|
+
|
|
124
|
+
Agent surface version: `3.1.0`. The CLI and both MCP transports implement the same 31 product operations.
|
|
125
|
+
|
|
126
|
+
| Product operation | CLI command | MCP tool | Required access | Confirm |
|
|
127
|
+
| --- | --- | --- | --- | --- |
|
|
128
|
+
| `projects.list` | `projects list` | `list_projects` | `read:projects` | No |
|
|
129
|
+
| `projects.get` | `projects show` | `get_project` | `read:projects; allowed project` | No |
|
|
130
|
+
| `projects.create` | `projects create` | `create_project` | `write:projects; unrestricted key` | No |
|
|
131
|
+
| `projects.update` | `projects update` | `update_project` | `write:projects; allowed project` | No |
|
|
132
|
+
| `projects.delete` | `projects delete` | `delete_project` | `write:projects; allowed project` | Yes |
|
|
133
|
+
| `feedback.list` | `feedback list` | `get_feedback` | `read:feedback; allowed project` | No |
|
|
134
|
+
| `feedback.get` | `feedback show` | `get_feedback_item` | `read:feedback; allowed project` | No |
|
|
135
|
+
| `feedback.search` | `feedback search` | `search_feedback` | `read:feedback; allowed project` | No |
|
|
136
|
+
| `feedback.create` | `feedback create` | `create_feedback` | `write:feedback; allowed project` | No |
|
|
137
|
+
| `feedback.update` | `feedback update` | `update_feedback` | `write:feedback; allowed project` | No |
|
|
138
|
+
| `feedback.delete` | `feedback delete` | `delete_feedback` | `write:feedback; allowed project` | Yes |
|
|
139
|
+
| `feedback.bulkUpdate` | `feedback bulk-update` | `bulk_update_feedback` | `write:feedback; allowed project` | Yes |
|
|
140
|
+
| `feedback.export` | `feedback export` | `export_feedback` | `read:feedback; allowed project` | No |
|
|
141
|
+
| `bugs.list` | `bugs list` | `get_bug_reports` | `read:feedback; allowed project` | No |
|
|
142
|
+
| `bugs.stats` | `bugs stats` | `get_bug_stats` | `read:feedback; allowed project` | No |
|
|
143
|
+
| `notes.create` | `feedback note` | `create_feedback_note` | `write:notes; allowed project` | No |
|
|
144
|
+
| `notes.update` | `feedback note update` | `update_feedback_note` | `write:notes; allowed project` | No |
|
|
145
|
+
| `notes.delete` | `feedback note delete` | `delete_feedback_note` | `write:notes; allowed project` | Yes |
|
|
146
|
+
| `replies.list` | `feedback replies` | `list_feedback_replies` | `read:feedback; allowed project` | No |
|
|
147
|
+
| `replies.send` | `feedback reply` | `send_feedback_reply` | `write:replies; allowed project` | Yes |
|
|
148
|
+
| `widget.getSettings` | `widget settings` | `get_widget_settings` | `read:projects; allowed project` | No |
|
|
149
|
+
| `widget.updateSettings` | `widget update`<br>`widget flow` | `update_widget_settings` | `write:widget; allowed project` | No |
|
|
150
|
+
| `widget.getScript` | `widget script` | `get_widget_script` | `read:projects; allowed project` | No |
|
|
151
|
+
| `mobile.get` | `mobile status`<br>`mobile verify` | `get_mobile_integration` | `read:projects; allowed project` | No |
|
|
152
|
+
| `mobile.update` | `mobile setup`<br>`mobile bundle`<br>`mobile conversations`<br>`mobile disable` | `update_mobile_integration` | `write:mobile; allowed project` | No |
|
|
153
|
+
| `mobile.rotateKey` | `mobile rotate-key` | `rotate_mobile_project_key` | `write:mobile; allowed project` | Yes |
|
|
154
|
+
| `waitlist.list` | `waitlist list` | `get_waitlist` | `read:feedback; allowed project` | No |
|
|
155
|
+
| `waitlist.export` | `waitlist export` | `export_waitlist` | `read:feedback; allowed project` | No |
|
|
156
|
+
| `team.list` | `team list` | `list_team_members` | `write:team; unrestricted key` | No |
|
|
157
|
+
| `team.updateRole` | `team role` | `update_team_member_role` | `write:team; unrestricted key` | Yes |
|
|
158
|
+
| `team.remove` | `team remove` | `remove_team_member` | `write:team; unrestricted key` | Yes |
|
|
159
|
+
<!-- END GENERATED AGENT CAPABILITIES -->
|
|
160
|
+
|
|
161
|
+
## Security & Privacy
|
|
162
|
+
|
|
163
|
+
- **Explicit access** — Read keys query data. Full keys can use approved write operations.
|
|
164
|
+
- **Project-level permissions** — Restricted keys can access only selected projects.
|
|
165
|
+
- **Confirmation** — High-impact operations do not run without explicit confirmation.
|
|
166
|
+
- **API key authentication** — Secure token-based authentication
|
|
167
|
+
- **Usage tracking** — Monitor API key usage from your Settings page
|
|
168
|
+
- **Revokable access** — Deactivate or delete API keys instantly
|
|
169
|
+
|
|
170
|
+
## Troubleshooting
|
|
171
|
+
|
|
172
|
+
### "Invalid or missing API key"
|
|
173
|
+
|
|
174
|
+
- Check that your API key starts with `fb_key_`
|
|
175
|
+
- Ensure the key is still active in Settings > MCP API Keys
|
|
176
|
+
- Verify the key has access to at least one project
|
|
177
|
+
|
|
178
|
+
### "No projects found"
|
|
179
|
+
|
|
180
|
+
- Make sure your API key has been granted access to projects
|
|
181
|
+
- Check that you have projects in your FeedbackBasket account
|
|
182
|
+
|
|
183
|
+
### Connection Issues
|
|
184
|
+
|
|
185
|
+
- Ensure Node.js 18+ is installed
|
|
186
|
+
- Try clearing npx cache: `npx clear-npx-cache`
|
|
187
|
+
- For local development, add `--base-url http://localhost:3000`
|
|
188
|
+
|
|
189
|
+
## API Key Management
|
|
190
|
+
|
|
191
|
+
Visit [feedbackbasket.com/dashboard/settings](https://feedbackbasket.com/dashboard/settings) to:
|
|
192
|
+
|
|
193
|
+
- Generate new API keys
|
|
194
|
+
- Manage project access
|
|
195
|
+
- View usage statistics
|
|
196
|
+
- Activate/deactivate keys
|
|
197
|
+
- Delete keys
|
|
198
|
+
|
|
199
|
+
## Links
|
|
200
|
+
|
|
201
|
+
- [FeedbackBasket](https://feedbackbasket.com)
|
|
202
|
+
- [GitHub Issues](https://github.com/deifos/feedbackbasket-mcp/issues)
|
package/dist/client.d.ts
CHANGED
|
@@ -1,50 +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
|
-
content: Array<{
|
|
6
|
-
type: string;
|
|
7
|
-
text: string;
|
|
8
|
-
}>;
|
|
9
|
-
}>;
|
|
10
|
-
getFeedback(params?: {
|
|
11
|
-
projectId?: string;
|
|
12
|
-
category?: string;
|
|
13
|
-
status?: string;
|
|
14
|
-
sentiment?: string;
|
|
15
|
-
limit?: number;
|
|
16
|
-
offset?: number;
|
|
17
|
-
search?: string;
|
|
18
|
-
includeNotes?: boolean;
|
|
19
|
-
}): Promise<{
|
|
20
|
-
content: Array<{
|
|
21
|
-
type: string;
|
|
22
|
-
text: string;
|
|
23
|
-
}>;
|
|
24
|
-
}>;
|
|
25
|
-
getBugReports(params?: {
|
|
26
|
-
projectId?: string;
|
|
27
|
-
status?: string;
|
|
28
|
-
severity?: 'high' | 'medium' | 'low';
|
|
29
|
-
limit?: number;
|
|
30
|
-
offset?: number;
|
|
31
|
-
search?: string;
|
|
32
|
-
includeNotes?: boolean;
|
|
33
|
-
}): Promise<{
|
|
34
|
-
content: Array<{
|
|
35
|
-
type: string;
|
|
36
|
-
text: string;
|
|
37
|
-
}>;
|
|
38
|
-
}>;
|
|
39
|
-
searchFeedback(query: string, options?: {
|
|
40
|
-
projectId?: string;
|
|
41
|
-
category?: string;
|
|
42
|
-
limit?: number;
|
|
43
|
-
}): Promise<{
|
|
44
|
-
content: Array<{
|
|
45
|
-
type: string;
|
|
46
|
-
text: string;
|
|
47
|
-
}>;
|
|
48
|
-
}>;
|
|
20
|
+
execute(operationId: ProductOperationId, rawArgs: Record<string, unknown>): Promise<McpOperationResult>;
|
|
49
21
|
private handleError;
|
|
50
22
|
}
|
package/dist/client.js
CHANGED
|
@@ -1,208 +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
|
-
async
|
|
16
|
-
|
|
17
|
-
const response = await this.api.post('/projects', {});
|
|
18
|
-
const projects = response.data.projects;
|
|
19
|
-
if (projects.length === 0) {
|
|
20
|
-
return {
|
|
21
|
-
content: [{
|
|
22
|
-
type: 'text',
|
|
23
|
-
text: 'No projects found. Make sure your API key has access to projects in your FeedbackBasket dashboard.'
|
|
24
|
-
}]
|
|
25
|
-
};
|
|
26
|
-
}
|
|
27
|
-
const projectList = projects.map(project => {
|
|
28
|
-
const openCount = project.byStatus['OPEN'] || 0;
|
|
29
|
-
const bugCount = project.byCategory['BUG'] || 0;
|
|
30
|
-
return [
|
|
31
|
-
`**${project.name}**`,
|
|
32
|
-
` ID: ${project.id}`,
|
|
33
|
-
` URL: ${project.url}`,
|
|
34
|
-
` Total Feedback: ${project.totalFeedback}`,
|
|
35
|
-
` Open: ${openCount} | Bugs: ${bugCount}`,
|
|
36
|
-
''
|
|
37
|
-
].join('\n');
|
|
38
|
-
}).join('\n');
|
|
39
|
-
return {
|
|
40
|
-
content: [{
|
|
41
|
-
type: 'text',
|
|
42
|
-
text: `# FeedbackBasket Projects (${projects.length} total)\n\n${projectList}`
|
|
43
|
-
}]
|
|
44
|
-
};
|
|
45
|
-
}
|
|
46
|
-
catch (error) {
|
|
47
|
-
throw this.handleError('Failed to fetch projects', error);
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
async getFeedback(params = {}) {
|
|
53
|
+
async execute(operationId, rawArgs) {
|
|
54
|
+
const request = createOperationRequest(operationId, rawArgs);
|
|
51
55
|
try {
|
|
52
|
-
const response = await this.api.
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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
|
+
}],
|
|
56
66
|
});
|
|
57
|
-
const
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
.filter(([, value]) => value !== undefined)
|
|
61
|
-
.map(([key, value]) => `${key}: ${value}`)
|
|
62
|
-
.join(', ');
|
|
63
|
-
return {
|
|
64
|
-
content: [{
|
|
65
|
-
type: 'text',
|
|
66
|
-
text: `No feedback found${filters ? ` with filters: ${filters}` : ''}.`
|
|
67
|
-
}]
|
|
68
|
-
};
|
|
69
|
-
}
|
|
70
|
-
const feedbackList = feedback.map(item => {
|
|
71
|
-
const category = item.category || 'UNCATEGORIZED';
|
|
72
|
-
const sentiment = item.sentiment || 'UNKNOWN';
|
|
73
|
-
const priority = item.aiPriorityScore != null
|
|
74
|
-
? ` | Priority: ${item.aiPriorityScore >= 70 ? 'HIGH' : item.aiPriorityScore >= 40 ? 'MEDIUM' : 'LOW'} (${item.aiPriorityScore}/100)`
|
|
75
|
-
: '';
|
|
76
|
-
const lines = [
|
|
77
|
-
`**${category} | ${sentiment} | ${item.status}${priority}**`,
|
|
78
|
-
`Project: ${item.project.name}`,
|
|
79
|
-
];
|
|
80
|
-
if (item.aiSummary)
|
|
81
|
-
lines.push(`Summary: ${item.aiSummary}`);
|
|
82
|
-
lines.push(`Content: ${item.content.length > 150 ? item.content.substring(0, 150) + '...' : item.content}`);
|
|
83
|
-
if (item.email)
|
|
84
|
-
lines.push(`Email: ${item.email}`);
|
|
85
|
-
if (item.pageUrl)
|
|
86
|
-
lines.push(`Page: ${item.pageUrl}`);
|
|
87
|
-
if (item.browser || item.os)
|
|
88
|
-
lines.push(`Browser: ${[item.browser, item.os, item.device].filter(Boolean).join(' | ')}`);
|
|
89
|
-
if (item.notes && item.notes.length > 0) {
|
|
90
|
-
lines.push(`Notes: ${item.notes.map(n => `[${n.author.name}] ${n.content}`).join('; ')}`);
|
|
91
|
-
}
|
|
92
|
-
lines.push(`Created: ${new Date(item.createdAt).toLocaleDateString()}`);
|
|
93
|
-
lines.push('');
|
|
94
|
-
return lines.join('\n');
|
|
95
|
-
}).join('\n');
|
|
96
|
-
const { pagination } = response.data;
|
|
67
|
+
const structuredContent = response.data && typeof response.data === 'object' && !Array.isArray(response.data)
|
|
68
|
+
? response.data
|
|
69
|
+
: { value: response.data };
|
|
97
70
|
return {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
text: [
|
|
101
|
-
`# Feedback Results (${feedback.length} of ${pagination.totalCount})\n`,
|
|
102
|
-
feedbackList,
|
|
103
|
-
pagination.hasMore ? `\n*Showing ${feedback.length} results. Use offset: ${pagination.offset + pagination.limit} to get more.*` : '',
|
|
104
|
-
].join('\n')
|
|
105
|
-
}]
|
|
71
|
+
structuredContent,
|
|
72
|
+
content: [{ type: 'text', text: JSON.stringify(structuredContent) }],
|
|
106
73
|
};
|
|
107
74
|
}
|
|
108
75
|
catch (error) {
|
|
109
|
-
throw this.handleError(
|
|
76
|
+
throw this.handleError(error);
|
|
110
77
|
}
|
|
111
78
|
}
|
|
112
|
-
|
|
113
|
-
try {
|
|
114
|
-
const response = await this.api.post('/feedback/bugs', {
|
|
115
|
-
limit: 20,
|
|
116
|
-
includeNotes: false,
|
|
117
|
-
...params,
|
|
118
|
-
});
|
|
119
|
-
const bugReports = response.data.bugReports;
|
|
120
|
-
if (bugReports.length === 0) {
|
|
121
|
-
const filters = Object.entries(params)
|
|
122
|
-
.filter(([, value]) => value !== undefined)
|
|
123
|
-
.map(([key, value]) => `${key}: ${value}`)
|
|
124
|
-
.join(', ');
|
|
125
|
-
return {
|
|
126
|
-
content: [{
|
|
127
|
-
type: 'text',
|
|
128
|
-
text: `No bug reports found${filters ? ` with filters: ${filters}` : ''}.`
|
|
129
|
-
}]
|
|
130
|
-
};
|
|
131
|
-
}
|
|
132
|
-
const bugList = bugReports.map(bug => {
|
|
133
|
-
const severityEmoji = bug.severity === 'high' ? '🔴' : bug.severity === 'medium' ? '🟡' : '🟢';
|
|
134
|
-
const statusMap = {
|
|
135
|
-
OPEN: '⏳', UNDER_REVIEW: '👁️', PLANNED: '📋',
|
|
136
|
-
IN_PROGRESS: '🔨', COMPLETE: '✅', CLOSED: '🔒'
|
|
137
|
-
};
|
|
138
|
-
const statusEmoji = statusMap[bug.status] || '❓';
|
|
139
|
-
const lines = [
|
|
140
|
-
`${severityEmoji} **${bug.severity.toUpperCase()} SEVERITY** ${statusEmoji} ${bug.status}`,
|
|
141
|
-
`Project: ${bug.project.name}`,
|
|
142
|
-
];
|
|
143
|
-
if (bug.aiSummary)
|
|
144
|
-
lines.push(`Summary: ${bug.aiSummary}`);
|
|
145
|
-
lines.push(`Bug: ${bug.content.length > 150 ? bug.content.substring(0, 150) + '...' : bug.content}`);
|
|
146
|
-
if (bug.email)
|
|
147
|
-
lines.push(`Reported by: ${bug.email}`);
|
|
148
|
-
if (bug.pageUrl)
|
|
149
|
-
lines.push(`Page: ${bug.pageUrl}`);
|
|
150
|
-
if (bug.browser || bug.os)
|
|
151
|
-
lines.push(`Browser: ${[bug.browser, bug.os, bug.device].filter(Boolean).join(' | ')}`);
|
|
152
|
-
lines.push(`Reported: ${new Date(bug.createdAt).toLocaleDateString()}`);
|
|
153
|
-
lines.push('');
|
|
154
|
-
return lines.join('\n');
|
|
155
|
-
}).join('\n');
|
|
156
|
-
const stats = response.data.stats;
|
|
157
|
-
const statsText = [
|
|
158
|
-
`## Bug Statistics`,
|
|
159
|
-
`Total Bugs: ${stats.total}`,
|
|
160
|
-
`🔴 High: ${stats.bySeverity.high} | 🟡 Medium: ${stats.bySeverity.medium} | 🟢 Low: ${stats.bySeverity.low}`,
|
|
161
|
-
`Open: ${stats.byStatus['OPEN'] || 0} | Under Review: ${stats.byStatus['UNDER_REVIEW'] || 0} | In Progress: ${stats.byStatus['IN_PROGRESS'] || 0} | Complete: ${stats.byStatus['COMPLETE'] || 0}`,
|
|
162
|
-
''
|
|
163
|
-
].join('\n');
|
|
164
|
-
const { pagination } = response.data;
|
|
165
|
-
return {
|
|
166
|
-
content: [{
|
|
167
|
-
type: 'text',
|
|
168
|
-
text: [
|
|
169
|
-
`# Bug Reports (${bugReports.length} of ${pagination.totalCount})\n`,
|
|
170
|
-
statsText,
|
|
171
|
-
bugList,
|
|
172
|
-
pagination.hasMore ? `\n*Showing ${bugReports.length} results. Use offset: ${pagination.offset + pagination.limit} to get more.*` : '',
|
|
173
|
-
].join('\n')
|
|
174
|
-
}]
|
|
175
|
-
};
|
|
176
|
-
}
|
|
177
|
-
catch (error) {
|
|
178
|
-
throw this.handleError('Failed to fetch bug reports', error);
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
async searchFeedback(query, options = {}) {
|
|
182
|
-
return this.getFeedback({
|
|
183
|
-
search: query,
|
|
184
|
-
limit: options.limit || 10,
|
|
185
|
-
...(options.projectId && { projectId: options.projectId }),
|
|
186
|
-
...(options.category && { category: options.category }),
|
|
187
|
-
});
|
|
188
|
-
}
|
|
189
|
-
handleError(message, error) {
|
|
79
|
+
handleError(error) {
|
|
190
80
|
if (error instanceof AxiosError) {
|
|
191
81
|
const status = error.response?.status;
|
|
192
|
-
const
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
return new Error(
|
|
204
|
-
}
|
|
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'}).`);
|
|
205
95
|
}
|
|
206
|
-
return new Error(
|
|
96
|
+
return new Error(error instanceof Error ? error.message : 'FeedbackBasket request failed.');
|
|
207
97
|
}
|
|
208
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,228 +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
|
-
|
|
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;
|
|
14
30
|
}
|
|
15
|
-
else if (arg.startsWith('--
|
|
16
|
-
|
|
17
|
-
|
|
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;
|
|
18
37
|
}
|
|
38
|
+
else if (arg.startsWith('--base-url='))
|
|
39
|
+
result.baseUrl = arg.slice('--base-url='.length);
|
|
19
40
|
}
|
|
20
|
-
return
|
|
21
|
-
}
|
|
22
|
-
const options = parseArgs(process.argv.slice(2));
|
|
23
|
-
const apiKey = options.apiKey || process.env.FEEDBACKBASKET_API_KEY;
|
|
24
|
-
const baseUrl = options.baseUrl || 'https://feedbackbasket.com';
|
|
25
|
-
if (!apiKey) {
|
|
26
|
-
console.error('Error: API key required.');
|
|
27
|
-
console.error('Usage: --api-key <key> or set FEEDBACKBASKET_API_KEY env var');
|
|
28
|
-
process.exit(1);
|
|
29
|
-
}
|
|
30
|
-
if (!apiKey.startsWith('fb_key_')) {
|
|
31
|
-
console.error('Error: Invalid API key format. Keys should start with "fb_key_".');
|
|
32
|
-
process.exit(1);
|
|
41
|
+
return result;
|
|
33
42
|
}
|
|
34
|
-
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
tools: [
|
|
45
|
-
{
|
|
46
|
-
name: 'list_projects',
|
|
47
|
-
description: 'List all FeedbackBasket projects accessible by your API key with summary statistics including feedback counts by status and category',
|
|
48
|
-
inputSchema: {
|
|
49
|
-
type: 'object',
|
|
50
|
-
properties: {},
|
|
51
|
-
additionalProperties: false,
|
|
52
|
-
},
|
|
53
|
-
},
|
|
54
|
-
{
|
|
55
|
-
name: 'get_feedback',
|
|
56
|
-
description: 'Get feedback from your FeedbackBasket projects with filtering. Returns AI analysis (summary, priority, category, sentiment), page URL, browser info, and optional notes.',
|
|
57
|
-
inputSchema: {
|
|
58
|
-
type: 'object',
|
|
59
|
-
properties: {
|
|
60
|
-
projectId: {
|
|
61
|
-
type: 'string',
|
|
62
|
-
description: 'Filter by specific project ID',
|
|
63
|
-
},
|
|
64
|
-
category: {
|
|
65
|
-
type: 'string',
|
|
66
|
-
enum: ['BUG', 'FEATURE_REQUEST', 'IMPROVEMENT', 'QUESTION'],
|
|
67
|
-
description: 'Filter by feedback category',
|
|
68
|
-
},
|
|
69
|
-
status: {
|
|
70
|
-
type: 'string',
|
|
71
|
-
enum: ['OPEN', 'UNDER_REVIEW', 'PLANNED', 'IN_PROGRESS', 'COMPLETE', 'CLOSED'],
|
|
72
|
-
description: 'Filter by feedback status',
|
|
73
|
-
},
|
|
74
|
-
sentiment: {
|
|
75
|
-
type: 'string',
|
|
76
|
-
enum: ['POSITIVE', 'NEGATIVE', 'NEUTRAL'],
|
|
77
|
-
description: 'Filter by sentiment analysis result',
|
|
78
|
-
},
|
|
79
|
-
search: {
|
|
80
|
-
type: 'string',
|
|
81
|
-
description: 'Search feedback content for specific text',
|
|
82
|
-
},
|
|
83
|
-
limit: {
|
|
84
|
-
type: 'number',
|
|
85
|
-
description: 'Maximum number of results (default: 20, max: 100)',
|
|
86
|
-
minimum: 1,
|
|
87
|
-
maximum: 100,
|
|
88
|
-
},
|
|
89
|
-
offset: {
|
|
90
|
-
type: 'number',
|
|
91
|
-
description: 'Offset for pagination (default: 0)',
|
|
92
|
-
minimum: 0,
|
|
93
|
-
},
|
|
94
|
-
includeNotes: {
|
|
95
|
-
type: 'boolean',
|
|
96
|
-
description: 'Include internal team notes (default: false)',
|
|
97
|
-
},
|
|
98
|
-
},
|
|
99
|
-
additionalProperties: false,
|
|
100
|
-
},
|
|
101
|
-
},
|
|
102
|
-
{
|
|
103
|
-
name: 'get_bug_reports',
|
|
104
|
-
description: 'Get bug reports from your FeedbackBasket projects with severity classification (high/medium/low based on sentiment) and statistics',
|
|
105
|
-
inputSchema: {
|
|
106
|
-
type: 'object',
|
|
107
|
-
properties: {
|
|
108
|
-
projectId: {
|
|
109
|
-
type: 'string',
|
|
110
|
-
description: 'Filter by specific project ID',
|
|
111
|
-
},
|
|
112
|
-
status: {
|
|
113
|
-
type: 'string',
|
|
114
|
-
enum: ['OPEN', 'UNDER_REVIEW', 'PLANNED', 'IN_PROGRESS', 'COMPLETE', 'CLOSED'],
|
|
115
|
-
description: 'Filter by bug status',
|
|
116
|
-
},
|
|
117
|
-
severity: {
|
|
118
|
-
type: 'string',
|
|
119
|
-
enum: ['high', 'medium', 'low'],
|
|
120
|
-
description: 'Filter by severity (high=negative sentiment, medium=neutral, low=positive)',
|
|
121
|
-
},
|
|
122
|
-
search: {
|
|
123
|
-
type: 'string',
|
|
124
|
-
description: 'Search bug report content',
|
|
125
|
-
},
|
|
126
|
-
limit: {
|
|
127
|
-
type: 'number',
|
|
128
|
-
description: 'Maximum number of results (default: 20, max: 100)',
|
|
129
|
-
minimum: 1,
|
|
130
|
-
maximum: 100,
|
|
131
|
-
},
|
|
132
|
-
offset: {
|
|
133
|
-
type: 'number',
|
|
134
|
-
description: 'Offset for pagination',
|
|
135
|
-
minimum: 0,
|
|
136
|
-
},
|
|
137
|
-
includeNotes: {
|
|
138
|
-
type: 'boolean',
|
|
139
|
-
description: 'Include internal team notes (default: false)',
|
|
140
|
-
},
|
|
141
|
-
},
|
|
142
|
-
additionalProperties: false,
|
|
143
|
-
},
|
|
144
|
-
},
|
|
145
|
-
{
|
|
146
|
-
name: 'search_feedback',
|
|
147
|
-
description: 'Search for feedback across all accessible projects using text search. Useful for finding specific issues or topics.',
|
|
148
|
-
inputSchema: {
|
|
149
|
-
type: 'object',
|
|
150
|
-
properties: {
|
|
151
|
-
query: {
|
|
152
|
-
type: 'string',
|
|
153
|
-
description: 'Search text to find in feedback content',
|
|
154
|
-
},
|
|
155
|
-
projectId: {
|
|
156
|
-
type: 'string',
|
|
157
|
-
description: 'Limit search to a specific project',
|
|
158
|
-
},
|
|
159
|
-
category: {
|
|
160
|
-
type: 'string',
|
|
161
|
-
enum: ['BUG', 'FEATURE_REQUEST', 'IMPROVEMENT', 'QUESTION'],
|
|
162
|
-
description: 'Filter search results by category',
|
|
163
|
-
},
|
|
164
|
-
limit: {
|
|
165
|
-
type: 'number',
|
|
166
|
-
description: 'Maximum number of results (default: 10)',
|
|
167
|
-
minimum: 1,
|
|
168
|
-
maximum: 50,
|
|
169
|
-
},
|
|
170
|
-
},
|
|
171
|
-
required: ['query'],
|
|
172
|
-
additionalProperties: false,
|
|
173
|
-
},
|
|
174
|
-
},
|
|
175
|
-
],
|
|
176
|
-
};
|
|
177
|
-
});
|
|
178
|
-
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
179
|
-
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);
|
|
180
53
|
try {
|
|
181
|
-
|
|
182
|
-
case 'list_projects':
|
|
183
|
-
return await client.listProjects();
|
|
184
|
-
case 'get_feedback':
|
|
185
|
-
return await client.getFeedback(args || {});
|
|
186
|
-
case 'get_bug_reports':
|
|
187
|
-
return await client.getBugReports(args || {});
|
|
188
|
-
case 'search_feedback':
|
|
189
|
-
if (!args || typeof args !== 'object' || !('query' in args) || typeof args.query !== 'string') {
|
|
190
|
-
throw new Error('Search query is required');
|
|
191
|
-
}
|
|
192
|
-
const searchOpts = {};
|
|
193
|
-
if (typeof args.projectId === 'string')
|
|
194
|
-
searchOpts.projectId = args.projectId;
|
|
195
|
-
if (typeof args.category === 'string')
|
|
196
|
-
searchOpts.category = args.category;
|
|
197
|
-
if (typeof args.limit === 'number')
|
|
198
|
-
searchOpts.limit = args.limit;
|
|
199
|
-
return await client.searchFeedback(args.query, searchOpts);
|
|
200
|
-
default:
|
|
201
|
-
throw new Error(`Unknown tool: ${name}`);
|
|
202
|
-
}
|
|
54
|
+
return await client.execute(operation.id, input);
|
|
203
55
|
}
|
|
204
56
|
catch (error) {
|
|
205
|
-
|
|
206
|
-
return {
|
|
207
|
-
content: [{ type: 'text', text: `Error: ${errorMessage}` }],
|
|
208
|
-
isError: true,
|
|
209
|
-
};
|
|
57
|
+
return errorResult(error instanceof Error ? error.message : 'FeedbackBasket request failed.');
|
|
210
58
|
}
|
|
211
|
-
}
|
|
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
|
+
}
|
|
212
74
|
async function main() {
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
|
|
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.');
|
|
220
88
|
process.exit(1);
|
|
221
|
-
}
|
|
89
|
+
});
|
|
222
90
|
}
|
|
223
|
-
process.on('SIGINT', () => process.exit(0));
|
|
224
|
-
process.on('SIGTERM', () => process.exit(0));
|
|
225
|
-
main().catch((error) => {
|
|
226
|
-
console.error('Fatal error:', error);
|
|
227
|
-
process.exit(1);
|
|
228
|
-
});
|
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": "node node_modules/typescript/bin/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.1.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.1.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
|
+
}
|