Skip to main content
Glama
server.smithery.js•12.6 kB
#!/usr/bin/env node const Database = require('better-sqlite3'); const path = require('path'); // Optimized MCP server with real database queries class OptimizedMCPServer { constructor() { this.db = null; this.tools = [ { name: 'search_local', description: 'Search the local EGW writings database (fast, limited results)', inputSchema: { type: 'object', properties: { query: { type: 'string', description: 'Search query' }, limit: { type: 'number', description: 'Maximum results (default: 10, max: 50)', default: 10 }, }, required: ['query'], }, }, { name: 'get_local_book', description: 'Get information about a specific book', inputSchema: { type: 'object', properties: { bookId: { type: 'number', description: 'Book ID' }, }, required: ['bookId'], }, }, { name: 'get_local_content', description: 'Get content from a specific book', inputSchema: { type: 'object', properties: { bookId: { type: 'number', description: 'Book ID' }, limit: { type: 'number', description: 'Maximum paragraphs (default: 10, max: 100)', default: 10 }, offset: { type: 'number', description: 'Offset for pagination (default: 0)', default: 0 }, }, required: ['bookId'], }, }, { name: 'list_local_books', description: 'List all available books', inputSchema: { type: 'object', properties: { language: { type: 'string', description: 'Language filter (default: "en")', default: 'en' }, limit: { type: 'number', description: 'Maximum books (default: 20, max: 100)', default: 20 }, }, }, }, { name: 'get_database_stats', description: 'Get database statistics (fast summary)', inputSchema: { type: 'object', properties: {}, }, }, ]; } initDatabase() { if (this.db) return; try { const dbPath = path.join(__dirname, '..', 'data', 'egw-writings.db'); this.db = new Database(dbPath, { readonly: true, fileMustExist: true, }); // Set pragmas for better performance this.db.pragma('journal_mode = WAL'); this.db.pragma('synchronous = NORMAL'); this.db.pragma('temp_store = MEMORY'); this.db.pragma('mmap_size = 30000000000'); this.db.pragma('page_size = 4096'); this.db.pragma('cache_size = -64000'); // 64MB cache // Prepare statements for reuse this.prepareStatements(); process.stderr.write('Database initialized successfully\n'); } catch (error) { process.stderr.write(`Database initialization error: ${error.message}\n`); throw error; } } prepareStatements() { // Optimized prepared statements this.stmts = { search: this.db.prepare(` SELECT p.para_id, p.book_id, b.code as pub_code, b.title as pub_name, b.author, b.pub_year, substr(p.content, 1, 200) as snippet FROM paragraphs p JOIN books b ON p.book_id = b.book_id WHERE p.content LIKE ? COLLATE NOCASE LIMIT ? `), getBook: this.db.prepare(` SELECT book_id, code, lang, type, title, author, pub_year, description FROM books WHERE book_id = ? `), getContent: this.db.prepare(` SELECT para_id, content, book_id FROM paragraphs WHERE book_id = ? ORDER BY para_id LIMIT ? OFFSET ? `), listBooks: this.db.prepare(` SELECT book_id, code, lang, type, title, author, pub_year FROM books WHERE lang = ? ORDER BY title LIMIT ? `), // Fast stats using cached counts stats: this.db.prepare(` SELECT (SELECT COUNT(DISTINCT book_id) FROM books) as total_books, (SELECT COUNT(DISTINCT book_id) FROM paragraphs) as books_with_content, (SELECT COUNT(DISTINCT lang) FROM books) as languages `), // Estimate paragraph count (fast) paragraphCount: this.db.prepare(` SELECT COUNT(*) as count FROM paragraphs LIMIT 100000 `) }; } async handleRequest(request) { try { const { method, params, id } = request; switch (method) { case 'initialize': this.initDatabase(); return { jsonrpc: '2.0', id, result: { protocolVersion: '2024-11-05', capabilities: { tools: {}, }, serverInfo: { name: 'egw-research-server', version: '1.0.0', }, }, }; case 'tools/list': return { jsonrpc: '2.0', id, result: { tools: this.tools, }, }; case 'tools/call': return await this.handleToolCall(params, id); case 'notifications/initialized': case 'notifications/cancelled': return null; default: return { jsonrpc: '2.0', id, error: { code: -32601, message: `Method not found: ${method}`, }, }; } } catch (error) { process.stderr.write(`Request error: ${error.message}\n`); return { jsonrpc: '2.0', id: request.id, error: { code: -32603, message: `Internal error: ${error.message}`, }, }; } } async handleToolCall(params, id) { const { name, arguments: args } = params; try { if (!this.db) { this.initDatabase(); } switch (name) { case 'search_local': { const { query, limit = 10 } = args; const safeLimit = Math.min(limit, 50); // Cap at 50 results const startTime = Date.now(); const results = this.stmts.search.all(`%${query}%`, safeLimit); const duration = Date.now() - startTime; return { jsonrpc: '2.0', id, result: { content: [ { type: 'text', text: JSON.stringify({ results: results.map((r, idx) => ({ index: idx, para_id: r.para_id, book_id: r.book_id, pub_code: r.pub_code, pub_name: r.pub_name, author: r.author, pub_year: r.pub_year, snippet: r.snippet + '...', refcode_short: `${r.pub_code} ${r.para_id}`, })), total: results.length, query: query, query_time_ms: duration, }, null, 2), }, ], }, }; } case 'get_local_book': { const { bookId } = args; const book = this.stmts.getBook.get(bookId); if (!book) { return { jsonrpc: '2.0', id, error: { code: -32602, message: `Book not found: ${bookId}`, }, }; } return { jsonrpc: '2.0', id, result: { content: [ { type: 'text', text: JSON.stringify(book, null, 2), }, ], }, }; } case 'get_local_content': { const { bookId, limit = 10, offset = 0 } = args; const safeLimit = Math.min(limit, 100); const paragraphs = this.stmts.getContent.all(bookId, safeLimit, offset); return { jsonrpc: '2.0', id, result: { content: [ { type: 'text', text: JSON.stringify({ bookId: bookId, paragraphs: paragraphs, count: paragraphs.length, offset: offset, }, null, 2), }, ], }, }; } case 'list_local_books': { const { language = 'en', limit = 20 } = args; const safeLimit = Math.min(limit, 100); const books = this.stmts.listBooks.all(language, safeLimit); return { jsonrpc: '2.0', id, result: { content: [ { type: 'text', text: JSON.stringify({ books: books, count: books.length, language: language, }, null, 2), }, ], }, }; } case 'get_database_stats': { const startTime = Date.now(); // Get fast stats const stats = this.stmts.stats.get(); // Get approximate paragraph count (with limit to prevent timeout) let paragraphCount = 0; try { const result = this.stmts.paragraphCount.get(); paragraphCount = result.count; } catch (error) { process.stderr.write(`Paragraph count error: ${error.message}\n`); paragraphCount = 'unavailable'; } const duration = Date.now() - startTime; return { jsonrpc: '2.0', id, result: { content: [ { type: 'text', text: JSON.stringify({ total_books: stats.total_books, books_with_content: stats.books_with_content, paragraphs: paragraphCount, languages: stats.languages, query_time_ms: duration, note: paragraphCount === 'unavailable' ? 'Paragraph count unavailable - database too large' : 'Stats retrieved successfully', }, null, 2), }, ], }, }; } default: return { jsonrpc: '2.0', id, error: { code: -32601, message: `Unknown tool: ${name}`, }, }; } } catch (error) { process.stderr.write(`Tool execution error: ${error.message}\n${error.stack}\n`); return { jsonrpc: '2.0', id, error: { code: -32603, message: `Tool execution error: ${error.message}`, }, }; } } async run() { process.stdin.setEncoding('utf8'); process.stdout.setEncoding('utf8'); let buffer = ''; process.stdin.on('data', async (chunk) => { buffer += chunk; const messages = buffer.split('\n'); buffer = messages.pop() || ''; for (const message of messages) { if (message.trim()) { try { const request = JSON.parse(message); const response = await this.handleRequest(request); if (response) { process.stdout.write(JSON.stringify(response) + '\n'); } } catch (error) { process.stderr.write(`Error processing request: ${error.message}\n`); } } } }); process.stdin.on('end', () => { if (this.db) { this.db.close(); } process.exit(0); }); process.on('SIGINT', () => { if (this.db) { this.db.close(); } process.exit(0); }); process.on('SIGTERM', () => { if (this.db) { this.db.close(); } process.exit(0); }); } } // Start the server const server = new OptimizedMCPServer(); server.run().catch(error => { process.stderr.write(`Server error: ${error.message}\n`); 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/pythondev-pro/egw_writings_mcp_server'

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