Skip to main content
Glama
ispyridis

Calibre RAG MCP Server

by ispyridis

search

Search your Calibre ebook library using natural language queries or metadata field syntax to find specific books and content.

Instructions

Search the Calibre ebook library. Supports both full-text content search (default) and metadata search using field syntax.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
fuzzy_fallbackNoAlternative search terms if exact query fails
limitNoMaximum number of results (default: 50)
queryYesSearch query. For full-text: use natural language. For metadata: use field syntax (author:Name, title:"Title").

Implementation Reference

  • Input schema for the 'search' tool defining the expected parameters: query (required string), optional limit and fuzzy_fallback.
    inputSchema: { type: 'object', properties: { query: { type: 'string', description: 'Search query. For full-text: use natural language. For metadata: use field syntax (author:Name, title:"Title").' }, limit: { type: 'integer', description: 'Maximum number of results (default: 50)', default: 50 }, fuzzy_fallback: { type: 'string', description: 'Alternative search terms if exact query fails' } }, required: ['query'] } },
  • The core handler function for the 'search' tool. Performs Calibre search to find matching book IDs, then retrieves detailed metadata and formats it for MCP response.
    async searchBooksMetadata(query, limit = 50) { try { const searchResult = await this.runCalibreCommand(['search', '--limit', limit.toString(), query]); if (!searchResult.trim()) { return []; } const bookIds = searchResult.trim(); const idQuery = `id:${bookIds.replace(/,/g, ' OR id:')}`; const listResult = await this.runCalibreCommand([ 'list', '--fields', 'id,title,authors,series,tags,publisher,pubdate,formats,identifiers,comments', '--for-machine', '--search', idQuery ]); const books = JSON.parse(listResult || '[]'); return books.map(book => ({ id: book.id, title: book.title, authors: book.authors, series: book.series, tags: book.tags, publisher: book.publisher, published: book.pubdate, epub_url: this.createEpubUrl(book.authors, book.title, book.id), formats: book.formats ? book.formats.map(f => path.basename(f)) : [], full_formats: book.formats || [], has_text: book.formats ? book.formats.some(f => f.endsWith('.txt')) : false, description: book.comments ? book.comments.replace(/<[^>]+>/g, '').split('\n').slice(0, 2).join(' ').substring(0, 200) + '...' : null })); } catch (error) { this.log(`Metadata search failed: ${error.message}`); return []; } }
  • server.js:1094-1106 (registration)
    Tool dispatch handler in handleToolsCall that invokes the searchBooksMetadata function and formats/sends the MCP response.
    case 'search': const query = args.query; const limit = args.limit || 50; if (!query) { this.sendError(id, -32602, 'Missing required parameter: query'); return; } const results = await this.searchBooksMetadata(query, limit); const mcpResult = this.formatResponse(results, query, 'search'); this.sendSuccess(id, mcpResult); break;
  • server.js:965-988 (registration)
    Registration of the 'search' tool in the tools/list response, including name, description, and schema.
    { name: 'search', description: 'Search the Calibre ebook library. Supports both full-text content search (default) and metadata search using field syntax.', inputSchema: { type: 'object', properties: { query: { type: 'string', description: 'Search query. For full-text: use natural language. For metadata: use field syntax (author:Name, title:"Title").' }, limit: { type: 'integer', description: 'Maximum number of results (default: 50)', default: 50 }, fuzzy_fallback: { type: 'string', description: 'Alternative search terms if exact query fails' } }, required: ['query'] } }, {
  • Helper function used by search handler to format the MCP response with content and results.
    formatResponse(searchResults, query, searchType = 'search') { const count = searchResults.length; if (count === 0) { return { content: [{ type: 'text', text: `No results found for: ${query}` }], results: [] }; } let contentText; let results; if (searchType === 'context') { contentText = `Found ${count} relevant context chunk(s) for '${query}':\n\n` + searchResults.map((chunk, i) => `**${i + 1}. From "${chunk.book_title}" by ${chunk.authors}** (similarity: ${chunk.similarity.toFixed(3)})\n` + `${chunk.text.substring(0, 200)}...\n\n` ).join(''); results = searchResults.map(chunk => ({ id: chunk.id, title: `${chunk.book_title} - Chunk ${chunk.metadata.chunk_index}`, text: chunk.text, similarity: chunk.similarity, metadata: chunk.metadata })); } else { contentText = `Found ${count} book(s) matching '${query}':\n\n` + searchResults.map(r => `• ${r.title} by ${r.authors}\n URL: ${r.epub_url}\n ${r.description ? 'Description: ' + r.description : ''}\n` ).join('\n'); results = searchResults.map(r => ({ id: r.id.toString(), title: r.title, text: r.description || `${r.title} by ${r.authors}`, url: r.epub_url })); } return { content: [{ type: 'text', text: contentText }], results: results }; }

Latest Blog Posts

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/ispyridis/calibre-rag-mcp-nodejs'

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