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
| Name | Required | Description | Default |
|---|---|---|---|
| fuzzy_fallback | No | Alternative search terms if exact query fails | |
| limit | No | Maximum number of results (default: 50) | |
| query | Yes | Search query. For full-text: use natural language. For metadata: use field syntax (author:Name, title:"Title"). |
Implementation Reference
- server.js:968-987 (schema)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'] } },
- server.js:823-864 (handler)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'] } }, {
- server.js:866-917 (helper)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 }; }