Skip to main content
Glama

FeedbackBasket MCP Server

by deifos
index.js9.79 kB
#!/usr/bin/env node import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextprotocol/sdk/types.js'; import { FeedbackBasketClient } from './client.js'; // Parse command line arguments manually (like Stripe does) function parseArgs(args) { const options = {}; args.forEach((arg) => { if (arg.startsWith('--')) { const [key, value] = arg.slice(2).split('='); if (key === 'api-key' && value) { options.apiKey = value; } else if (key === 'base-url' && value) { options.baseUrl = value; } } }); return options; } const options = parseArgs(process.argv.slice(2)); const apiKey = options.apiKey || process.env.FEEDBACKBASKET_API_KEY; const baseUrl = options.baseUrl || 'https://feedbackbasket.com'; if (!apiKey) { console.error('Error: API key required.'); console.error('Usage: Use --api-key option or set FEEDBACKBASKET_API_KEY environment variable'); process.exit(1); } // Validate API key format if (!apiKey.startsWith('fb_key_')) { console.error('Error: Invalid API key format. API keys should start with "fb_key_".'); process.exit(1); } console.log('API key format accepted:', apiKey.substring(0, 20) + '...'); const client = new FeedbackBasketClient(apiKey, baseUrl); // Create MCP server const server = new Server({ name: 'feedbackbasket-mcp', version: '1.0.0', capabilities: { tools: {}, }, }); // List available tools server.setRequestHandler(ListToolsRequestSchema, async (_request) => { return { tools: [ { name: 'list_projects', description: 'List all FeedbackBasket projects accessible by your API key with summary statistics', inputSchema: { type: 'object', properties: {}, additionalProperties: false, }, }, { name: 'get_feedback', description: 'Get feedback from your FeedbackBasket projects with filtering options', inputSchema: { type: 'object', properties: { projectId: { type: 'string', description: 'Filter by specific project ID', }, category: { type: 'string', enum: ['BUG', 'FEATURE', 'REVIEW'], description: 'Filter by feedback category', }, status: { type: 'string', enum: ['PENDING', 'REVIEWED', 'DONE'], description: 'Filter by feedback status', }, sentiment: { type: 'string', enum: ['POSITIVE', 'NEGATIVE', 'NEUTRAL'], description: 'Filter by sentiment analysis result', }, search: { type: 'string', description: 'Search feedback content for specific text', }, limit: { type: 'number', description: 'Maximum number of results to return (default: 20, max: 100)', minimum: 1, maximum: 100, }, includeNotes: { type: 'boolean', description: 'Include internal notes in the response (default: false)', }, }, additionalProperties: false, }, }, { name: 'get_bug_reports', description: 'Get bug reports specifically from your FeedbackBasket projects', inputSchema: { type: 'object', properties: { projectId: { type: 'string', description: 'Filter by specific project ID', }, status: { type: 'string', enum: ['PENDING', 'REVIEWED', 'DONE'], description: 'Filter by bug status', }, severity: { type: 'string', enum: ['high', 'medium', 'low'], description: 'Filter by computed severity (based on sentiment: negative=high, neutral=medium, positive=low)', }, search: { type: 'string', description: 'Search bug report content for specific text', }, limit: { type: 'number', description: 'Maximum number of results to return (default: 20, max: 100)', minimum: 1, maximum: 100, }, includeNotes: { type: 'boolean', description: 'Include internal notes in the response (default: false)', }, }, additionalProperties: false, }, }, { name: 'search_feedback', description: 'Search for feedback across all accessible projects using text search', inputSchema: { type: 'object', properties: { query: { type: 'string', description: 'Search query to find in feedback content', }, projectId: { type: 'string', description: 'Limit search to specific project', }, category: { type: 'string', enum: ['BUG', 'FEATURE', 'REVIEW'], description: 'Filter search results by category', }, limit: { type: 'number', description: 'Maximum number of results (default: 10)', minimum: 1, maximum: 50, }, }, required: ['query'], additionalProperties: false, }, }, ], }; }); // Handle tool calls server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; try { switch (name) { case 'list_projects': return await client.listProjects(); case 'get_feedback': return await client.getFeedback(args || {}); case 'get_bug_reports': return await client.getBugReports(args || {}); case 'search_feedback': if (!args || typeof args !== 'object' || !('query' in args) || typeof args.query !== 'string') { throw new Error('Search query is required'); } const searchOptions = {}; if (typeof args.projectId === 'string') { searchOptions.projectId = args.projectId; } if (typeof args.category === 'string' && ['BUG', 'FEATURE', 'REVIEW'].includes(args.category)) { searchOptions.category = args.category; } if (typeof args.limit === 'number') { searchOptions.limit = args.limit; } return await client.searchFeedback(args.query, searchOptions); default: throw new Error(`Unknown tool: ${name}`); } } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred'; return { content: [ { type: 'text', text: `Error: ${errorMessage}`, }, ], isError: true, }; } }); // Start the server async function main() { try { const transport = new StdioServerTransport(); await server.connect(transport); // We use console.error instead of console.log since console.log will output to stdio, which will confuse the MCP server console.error('✅ FeedbackBasket MCP server started successfully'); } catch (error) { console.error('🚨 Failed to start MCP server:', error); process.exit(1); } } // Handle graceful shutdown process.on('SIGINT', () => { console.error('Received SIGINT, shutting down gracefully...'); process.exit(0); }); process.on('SIGTERM', () => { console.error('Received SIGTERM, shutting down gracefully...'); process.exit(0); }); // Only run if this file is the main module main().catch((error) => { console.error('🚨 Fatal error:', error); process.exit(1); });

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/deifos/feedbackbasket-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server