UNPKG

ai-flashmob-mcp

Version:

MCP server for AI-powered flashcard generation

240 lines (216 loc) 7.95 kB
#!/usr/bin/env node /** * Flashcard Generator MCP Server * * This Model Context Protocol (MCP) server provides tools for generating * AI-powered flashcards from text or images using the AI Flashmob backend. * * Usage: * - Configure with your PUBLIC_USER_ID and SECRET_KEY * - Connect to Claude Desktop or other MCP-compatible clients * - Use flashcard generation and management tools */ import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { CallToolRequestSchema, ErrorCode, ListToolsRequestSchema, McpError, } from '@modelcontextprotocol/sdk/types.js'; import { FlashcardGenerator } from './tools/flashcard-generator.js'; import { FlashcardManager } from './tools/flashcard-manager.js'; class FlashcardMcpServer { constructor() { this.server = new Server( { name: 'ai-flashmob-mcp', version: '0.0.1', }, { capabilities: { tools: {}, }, } ); this.flashcardGenerator = new FlashcardGenerator(); this.flashcardManager = new FlashcardManager(); this.setupToolHandlers(); } setupToolHandlers() { // List available tools this.server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: [ { name: 'generate_flashcards_from_text', description: 'Generate AI flashcards from text content. Creates educational flashcards and saves them to your account.', inputSchema: { type: 'object', properties: { text: { type: 'string', description: 'The text content to generate flashcards from (10-4000 characters)', minLength: 10, maxLength: 4000 }, maxCards: { type: 'integer', description: 'Maximum number of flashcards to generate (1-10)', minimum: 1, maximum: 10, default: 5 }, deckId: { type: 'integer', description: 'Optional deck ID to organize the flashcards', minimum: 1 } }, required: ['text'] } }, { name: 'generate_flashcards_from_image', description: 'Generate AI flashcards from image content. Analyzes visual content and creates educational flashcards.', inputSchema: { type: 'object', properties: { image: { type: 'string', description: 'Base64-encoded image data (with or without data URL prefix)' }, imageFormat: { type: 'string', description: 'Image format (png, jpeg, jpg, webp)', enum: ['png', 'jpeg', 'jpg', 'webp'], default: 'png' }, maxCards: { type: 'integer', description: 'Maximum number of flashcards to generate (1-10)', minimum: 1, maximum: 10, default: 5 }, deckId: { type: 'integer', description: 'Optional deck ID to organize the flashcards', minimum: 1 } }, required: ['image'] } }, { name: 'get_decks', description: 'Get all decks for the authenticated user. Shows deck details including flashcard counts.', inputSchema: { type: 'object', properties: {}, additionalProperties: false } }, { name: 'get_deck_cards', description: 'Get all flashcards from a specific deck. Shows detailed flashcard content and study progress.', inputSchema: { type: 'object', properties: { deckId: { type: 'integer', description: 'The ID of the deck to get flashcards from', minimum: 1 } }, required: ['deckId'] } } ] }; }); // Handle tool calls this.server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; try { switch (name) { case 'generate_flashcards_from_text': return await this.flashcardGenerator.generateFromText(args); case 'generate_flashcards_from_image': return await this.flashcardGenerator.generateFromImage(args); case 'get_decks': return await this.flashcardManager.getDecks(args); case 'get_deck_cards': return await this.flashcardManager.getDeckCards(args); default: throw new McpError( ErrorCode.MethodNotFound, `Unknown tool: ${name}` ); } } catch (error) { // If it's already an McpError, re-throw it if (error instanceof McpError) { throw error; } // Convert other errors to McpError throw new McpError( ErrorCode.InternalError, `Tool execution failed: ${error.message}` ); } }); } async run() { const transport = new StdioServerTransport(); await this.server.connect(transport); // Log startup message to stderr (won't interfere with MCP protocol) console.error('🤖 AI Flashmob MCP Server started'); console.error('📚 Ready to generate and manage AI-powered flashcards!'); console.error('🔑 Make sure PUBLIC_USER_ID and SECRET_KEY environment variables are set'); console.error('🛠️ Available tools: generate_flashcards_from_text, generate_flashcards_from_image, get_decks, get_deck_cards'); } } // Configuration validation function validateConfig() { const requiredVars = ['PUBLIC_USER_ID', 'SECRET_KEY']; const missing = requiredVars.filter(varName => !process.env[varName]); if (missing.length > 0) { console.error('❌ Missing required environment variables:', missing.join(', ')); console.error('📝 Please set these variables before starting the MCP server:'); console.error(' export PUBLIC_USER_ID="your-public-user-id"'); console.error(' export SECRET_KEY="your-secret-key"'); console.error(' export API_BASE_URL="https://your-api.com" # Optional, defaults to https://api.ai-flashmob.com'); process.exit(1); } // Validate format if (process.env.SECRET_KEY.length !== 64) { console.error('❌ SECRET_KEY should be 64 characters long'); process.exit(1); } // Log configuration (without sensitive data) console.error('✅ Configuration validated'); console.error(`🆔 Public User ID: ${process.env.PUBLIC_USER_ID}`); console.error(`🔐 Secret Key: ${process.env.SECRET_KEY.substring(0, 8)}...`); console.error(`🌐 API Base URL: ${process.env.API_BASE_URL || 'https://api.ai-flashmob.com'}`); } // Error handling for unhandled rejections process.on('unhandledRejection', (reason, promise) => { console.error('Unhandled Rejection at:', promise, 'reason:', reason); process.exit(1); }); process.on('uncaughtException', (error) => { console.error('Uncaught Exception:', error); process.exit(1); }); // Main execution async function main() { validateConfig(); const server = new FlashcardMcpServer(); await server.run(); } // Start the server main().catch((error) => { console.error('Failed to start MCP server:', error); process.exit(1); });