@bbearai/mcp-server 0.3.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/dist/index-api.d.ts +12 -0
- package/dist/index-api.js +368 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +3577 -0
- package/package.json +29 -0
- package/src/index-api.ts +403 -0
- package/src/index.ts +4084 -0
- package/tsconfig.json +16 -0
package/package.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@bbearai/mcp-server",
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"description": "MCP server for BugBear - allows Claude Code to query bug reports",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"bugbear-mcp": "./dist/index.js",
|
|
8
|
+
"bugbear-mcp-api": "./dist/index-api.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsc",
|
|
12
|
+
"dev": "tsx watch src/index.ts",
|
|
13
|
+
"dev:api": "tsx watch src/index-api.ts",
|
|
14
|
+
"start": "node dist/index.js",
|
|
15
|
+
"start:api": "node dist/index-api.js"
|
|
16
|
+
},
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"@modelcontextprotocol/sdk": "^1.25.3",
|
|
19
|
+
"@supabase/supabase-js": "^2.39.0"
|
|
20
|
+
},
|
|
21
|
+
"devDependencies": {
|
|
22
|
+
"@types/node": "^20.0.0",
|
|
23
|
+
"tsx": "^4.0.0",
|
|
24
|
+
"typescript": "^5.0.0"
|
|
25
|
+
},
|
|
26
|
+
"engines": {
|
|
27
|
+
"node": ">=18.0.0"
|
|
28
|
+
}
|
|
29
|
+
}
|
package/src/index-api.ts
ADDED
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* BugBear MCP Server (API Version)
|
|
4
|
+
*
|
|
5
|
+
* This version uses the BugBear API instead of connecting directly to Supabase.
|
|
6
|
+
* This is the recommended setup for new projects.
|
|
7
|
+
*
|
|
8
|
+
* Configuration:
|
|
9
|
+
* BUGBEAR_API_KEY - Your project's API key (bb_live_xxx)
|
|
10
|
+
* BUGBEAR_API_URL - API URL (optional, defaults to https://bugbear.ai/api/v1)
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
14
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
15
|
+
import {
|
|
16
|
+
CallToolRequestSchema,
|
|
17
|
+
ListToolsRequestSchema,
|
|
18
|
+
} from '@modelcontextprotocol/sdk/types.js';
|
|
19
|
+
|
|
20
|
+
// Configuration
|
|
21
|
+
const API_KEY = process.env.BUGBEAR_API_KEY || '';
|
|
22
|
+
// Default to production URL, but allow override for local development
|
|
23
|
+
const API_URL = process.env.BUGBEAR_API_URL || 'https://app.bugbear.ai/api/v1';
|
|
24
|
+
|
|
25
|
+
// Validate configuration
|
|
26
|
+
function validateConfig() {
|
|
27
|
+
if (!API_KEY) {
|
|
28
|
+
console.error('BugBear MCP Server: BUGBEAR_API_KEY environment variable is required');
|
|
29
|
+
console.error('Get your API key from: https://bugbear.ai/settings/projects');
|
|
30
|
+
process.exit(1);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (!API_KEY.startsWith('bb_live_') && !API_KEY.startsWith('bb_test_')) {
|
|
34
|
+
console.error('BugBear MCP Server: Invalid API key format');
|
|
35
|
+
console.error('API keys should start with bb_live_ or bb_test_');
|
|
36
|
+
process.exit(1);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// API helper
|
|
41
|
+
async function apiRequest(
|
|
42
|
+
endpoint: string,
|
|
43
|
+
method: 'GET' | 'POST' | 'PATCH' = 'GET',
|
|
44
|
+
body?: Record<string, unknown>
|
|
45
|
+
): Promise<{ data?: unknown; error?: string }> {
|
|
46
|
+
try {
|
|
47
|
+
const url = `${API_URL}${endpoint}`;
|
|
48
|
+
const options: RequestInit = {
|
|
49
|
+
method,
|
|
50
|
+
headers: {
|
|
51
|
+
'Authorization': `Bearer ${API_KEY}`,
|
|
52
|
+
'Content-Type': 'application/json',
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
if (body && method !== 'GET') {
|
|
57
|
+
options.body = JSON.stringify(body);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const response = await fetch(url, options);
|
|
61
|
+
const json = await response.json();
|
|
62
|
+
|
|
63
|
+
if (!response.ok) {
|
|
64
|
+
return { error: json.error || `API error: ${response.status}` };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return { data: json.data };
|
|
68
|
+
} catch (error) {
|
|
69
|
+
return { error: error instanceof Error ? error.message : 'API request failed' };
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Tool definitions
|
|
74
|
+
const tools = [
|
|
75
|
+
{
|
|
76
|
+
name: 'list_reports',
|
|
77
|
+
description: 'List recent bug reports for the project',
|
|
78
|
+
inputSchema: {
|
|
79
|
+
type: 'object' as const,
|
|
80
|
+
properties: {
|
|
81
|
+
limit: {
|
|
82
|
+
type: 'number',
|
|
83
|
+
description: 'Maximum reports to return (default: 10, max: 50)',
|
|
84
|
+
},
|
|
85
|
+
status: {
|
|
86
|
+
type: 'string',
|
|
87
|
+
enum: ['new', 'reviewed', 'in_progress', 'resolved', 'closed'],
|
|
88
|
+
description: 'Filter by status',
|
|
89
|
+
},
|
|
90
|
+
severity: {
|
|
91
|
+
type: 'string',
|
|
92
|
+
enum: ['critical', 'high', 'medium', 'low'],
|
|
93
|
+
description: 'Filter by severity',
|
|
94
|
+
},
|
|
95
|
+
report_type: {
|
|
96
|
+
type: 'string',
|
|
97
|
+
enum: ['bug', 'test_fail', 'feedback', 'suggestion'],
|
|
98
|
+
description: 'Filter by type',
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
name: 'get_report',
|
|
105
|
+
description: 'Get detailed information about a specific report',
|
|
106
|
+
inputSchema: {
|
|
107
|
+
type: 'object' as const,
|
|
108
|
+
properties: {
|
|
109
|
+
report_id: {
|
|
110
|
+
type: 'string',
|
|
111
|
+
description: 'The UUID of the report',
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
required: ['report_id'],
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
name: 'update_report_status',
|
|
119
|
+
description: 'Update the status of a report',
|
|
120
|
+
inputSchema: {
|
|
121
|
+
type: 'object' as const,
|
|
122
|
+
properties: {
|
|
123
|
+
report_id: {
|
|
124
|
+
type: 'string',
|
|
125
|
+
description: 'The UUID of the report',
|
|
126
|
+
},
|
|
127
|
+
status: {
|
|
128
|
+
type: 'string',
|
|
129
|
+
enum: ['new', 'reviewed', 'in_progress', 'resolved', 'closed'],
|
|
130
|
+
description: 'New status',
|
|
131
|
+
},
|
|
132
|
+
resolution: {
|
|
133
|
+
type: 'string',
|
|
134
|
+
description: 'Resolution notes (optional)',
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
required: ['report_id', 'status'],
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
name: 'create_bug_report',
|
|
142
|
+
description: 'Create a new bug report from Claude Code',
|
|
143
|
+
inputSchema: {
|
|
144
|
+
type: 'object' as const,
|
|
145
|
+
properties: {
|
|
146
|
+
description: {
|
|
147
|
+
type: 'string',
|
|
148
|
+
description: 'Detailed description of the bug',
|
|
149
|
+
},
|
|
150
|
+
severity: {
|
|
151
|
+
type: 'string',
|
|
152
|
+
enum: ['critical', 'high', 'medium', 'low'],
|
|
153
|
+
description: 'Bug severity',
|
|
154
|
+
},
|
|
155
|
+
file_path: {
|
|
156
|
+
type: 'string',
|
|
157
|
+
description: 'File path where the bug was found',
|
|
158
|
+
},
|
|
159
|
+
line_number: {
|
|
160
|
+
type: 'number',
|
|
161
|
+
description: 'Line number',
|
|
162
|
+
},
|
|
163
|
+
code_snippet: {
|
|
164
|
+
type: 'string',
|
|
165
|
+
description: 'Relevant code snippet',
|
|
166
|
+
},
|
|
167
|
+
suggested_fix: {
|
|
168
|
+
type: 'string',
|
|
169
|
+
description: 'Suggested fix',
|
|
170
|
+
},
|
|
171
|
+
},
|
|
172
|
+
required: ['description', 'severity'],
|
|
173
|
+
},
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
name: 'get_project_info',
|
|
177
|
+
description: 'Get project information and statistics',
|
|
178
|
+
inputSchema: {
|
|
179
|
+
type: 'object' as const,
|
|
180
|
+
properties: {},
|
|
181
|
+
},
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
name: 'list_test_cases',
|
|
185
|
+
description: 'List test cases for the project',
|
|
186
|
+
inputSchema: {
|
|
187
|
+
type: 'object' as const,
|
|
188
|
+
properties: {
|
|
189
|
+
limit: {
|
|
190
|
+
type: 'number',
|
|
191
|
+
description: 'Max test cases to return (default: 50)',
|
|
192
|
+
},
|
|
193
|
+
priority: {
|
|
194
|
+
type: 'string',
|
|
195
|
+
enum: ['P0', 'P1', 'P2', 'P3'],
|
|
196
|
+
description: 'Filter by priority',
|
|
197
|
+
},
|
|
198
|
+
},
|
|
199
|
+
},
|
|
200
|
+
},
|
|
201
|
+
{
|
|
202
|
+
name: 'create_test_case',
|
|
203
|
+
description: 'Create a new test case',
|
|
204
|
+
inputSchema: {
|
|
205
|
+
type: 'object' as const,
|
|
206
|
+
properties: {
|
|
207
|
+
test_key: {
|
|
208
|
+
type: 'string',
|
|
209
|
+
description: 'Unique test ID (e.g., TC-001)',
|
|
210
|
+
},
|
|
211
|
+
title: {
|
|
212
|
+
type: 'string',
|
|
213
|
+
description: 'Test title',
|
|
214
|
+
},
|
|
215
|
+
description: {
|
|
216
|
+
type: 'string',
|
|
217
|
+
description: 'Test description',
|
|
218
|
+
},
|
|
219
|
+
steps: {
|
|
220
|
+
type: 'array',
|
|
221
|
+
items: {
|
|
222
|
+
type: 'object',
|
|
223
|
+
properties: {
|
|
224
|
+
stepNumber: { type: 'number' },
|
|
225
|
+
action: { type: 'string' },
|
|
226
|
+
expectedResult: { type: 'string' },
|
|
227
|
+
},
|
|
228
|
+
},
|
|
229
|
+
description: 'Test steps',
|
|
230
|
+
},
|
|
231
|
+
expected_result: {
|
|
232
|
+
type: 'string',
|
|
233
|
+
description: 'Expected outcome',
|
|
234
|
+
},
|
|
235
|
+
priority: {
|
|
236
|
+
type: 'string',
|
|
237
|
+
enum: ['P0', 'P1', 'P2', 'P3'],
|
|
238
|
+
description: 'Priority level',
|
|
239
|
+
},
|
|
240
|
+
},
|
|
241
|
+
required: ['test_key', 'title', 'steps', 'expected_result'],
|
|
242
|
+
},
|
|
243
|
+
},
|
|
244
|
+
{
|
|
245
|
+
name: 'get_qa_tracks',
|
|
246
|
+
description: 'Get QA tracks for the project',
|
|
247
|
+
inputSchema: {
|
|
248
|
+
type: 'object' as const,
|
|
249
|
+
properties: {},
|
|
250
|
+
},
|
|
251
|
+
},
|
|
252
|
+
{
|
|
253
|
+
name: 'verify_connection',
|
|
254
|
+
description: 'Verify the API connection is working',
|
|
255
|
+
inputSchema: {
|
|
256
|
+
type: 'object' as const,
|
|
257
|
+
properties: {},
|
|
258
|
+
},
|
|
259
|
+
},
|
|
260
|
+
];
|
|
261
|
+
|
|
262
|
+
// Tool handlers
|
|
263
|
+
async function handleTool(
|
|
264
|
+
name: string,
|
|
265
|
+
args: Record<string, unknown>
|
|
266
|
+
): Promise<{ content: Array<{ type: 'text'; text: string }> }> {
|
|
267
|
+
let result: { data?: unknown; error?: string };
|
|
268
|
+
|
|
269
|
+
switch (name) {
|
|
270
|
+
case 'list_reports': {
|
|
271
|
+
const params = new URLSearchParams();
|
|
272
|
+
if (args.limit) params.set('limit', String(args.limit));
|
|
273
|
+
if (args.status) params.set('status', String(args.status));
|
|
274
|
+
if (args.severity) params.set('severity', String(args.severity));
|
|
275
|
+
if (args.report_type) params.set('report_type', String(args.report_type));
|
|
276
|
+
result = await apiRequest(`/reports?${params.toString()}`);
|
|
277
|
+
break;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
case 'get_report':
|
|
281
|
+
result = await apiRequest(`/reports/${args.report_id}`);
|
|
282
|
+
break;
|
|
283
|
+
|
|
284
|
+
case 'update_report_status':
|
|
285
|
+
result = await apiRequest(`/reports/${args.report_id}`, 'PATCH', {
|
|
286
|
+
status: args.status,
|
|
287
|
+
resolution: args.resolution,
|
|
288
|
+
});
|
|
289
|
+
break;
|
|
290
|
+
|
|
291
|
+
case 'create_bug_report': {
|
|
292
|
+
// Build enhanced description with code context
|
|
293
|
+
let description = String(args.description);
|
|
294
|
+
if (args.file_path) {
|
|
295
|
+
description += `\n\n📁 File: ${args.file_path}`;
|
|
296
|
+
if (args.line_number) {
|
|
297
|
+
description += `:${args.line_number}`;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
if (args.code_snippet) {
|
|
301
|
+
description += `\n\n\`\`\`\n${args.code_snippet}\n\`\`\``;
|
|
302
|
+
}
|
|
303
|
+
if (args.suggested_fix) {
|
|
304
|
+
description += `\n\n💡 Suggested fix: ${args.suggested_fix}`;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
result = await apiRequest('/reports', 'POST', {
|
|
308
|
+
report_type: 'bug',
|
|
309
|
+
description,
|
|
310
|
+
severity: args.severity,
|
|
311
|
+
app_context: {
|
|
312
|
+
source: 'claude_code',
|
|
313
|
+
file_path: args.file_path,
|
|
314
|
+
line_number: args.line_number,
|
|
315
|
+
},
|
|
316
|
+
});
|
|
317
|
+
break;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
case 'get_project_info':
|
|
321
|
+
result = await apiRequest('/project');
|
|
322
|
+
break;
|
|
323
|
+
|
|
324
|
+
case 'list_test_cases': {
|
|
325
|
+
const params = new URLSearchParams();
|
|
326
|
+
if (args.limit) params.set('limit', String(args.limit));
|
|
327
|
+
if (args.priority) params.set('priority', String(args.priority));
|
|
328
|
+
result = await apiRequest(`/test-cases?${params.toString()}`);
|
|
329
|
+
break;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
case 'create_test_case':
|
|
333
|
+
result = await apiRequest('/test-cases', 'POST', {
|
|
334
|
+
test_key: args.test_key,
|
|
335
|
+
title: args.title,
|
|
336
|
+
description: args.description,
|
|
337
|
+
steps: args.steps,
|
|
338
|
+
expected_result: args.expected_result,
|
|
339
|
+
priority: args.priority || 'P2',
|
|
340
|
+
});
|
|
341
|
+
break;
|
|
342
|
+
|
|
343
|
+
case 'get_qa_tracks':
|
|
344
|
+
result = await apiRequest('/qa-tracks');
|
|
345
|
+
break;
|
|
346
|
+
|
|
347
|
+
case 'verify_connection':
|
|
348
|
+
result = await apiRequest('/verify');
|
|
349
|
+
break;
|
|
350
|
+
|
|
351
|
+
default:
|
|
352
|
+
result = { error: `Unknown tool: ${name}` };
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
if (result.error) {
|
|
356
|
+
return {
|
|
357
|
+
content: [{ type: 'text', text: `Error: ${result.error}` }],
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
return {
|
|
362
|
+
content: [{ type: 'text', text: JSON.stringify(result.data, null, 2) }],
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// Main
|
|
367
|
+
async function main() {
|
|
368
|
+
validateConfig();
|
|
369
|
+
|
|
370
|
+
const server = new Server(
|
|
371
|
+
{
|
|
372
|
+
name: 'bugbear',
|
|
373
|
+
version: '0.2.0',
|
|
374
|
+
},
|
|
375
|
+
{
|
|
376
|
+
capabilities: {
|
|
377
|
+
tools: {},
|
|
378
|
+
},
|
|
379
|
+
}
|
|
380
|
+
);
|
|
381
|
+
|
|
382
|
+
// List tools
|
|
383
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
384
|
+
tools,
|
|
385
|
+
}));
|
|
386
|
+
|
|
387
|
+
// Handle tool calls
|
|
388
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
389
|
+
const { name, arguments: args } = request.params;
|
|
390
|
+
return await handleTool(name, (args || {}) as Record<string, unknown>);
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
// Connect via stdio
|
|
394
|
+
const transport = new StdioServerTransport();
|
|
395
|
+
await server.connect(transport);
|
|
396
|
+
|
|
397
|
+
console.error('BugBear MCP Server (API) connected');
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
main().catch((error) => {
|
|
401
|
+
console.error('Fatal error:', error);
|
|
402
|
+
process.exit(1);
|
|
403
|
+
});
|