Skip to main content
Glama
ispyridis

Calibre RAG MCP Server

by ispyridis

add_books_to_project

Add books to a RAG project for vectorization and semantic search, enabling contextual retrieval from your Calibre ebook library.

Instructions

Add books to a RAG project for vectorization and context search

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
book_idsYesArray of book IDs to add to the project
project_nameYesName of the project

Implementation Reference

  • Main handler function that adds specified books to a RAG project. Fetches book metadata from Calibre, extracts text content (prioritizing .txt formats, falling back to OCR for PDFs/images), intelligently chunks the content, generates embeddings using transformers, saves individual chunks, vectors, metadata, and updates project configuration.
    async addBooksToProject(projectName, bookIds) { const project = this.projects.get(projectName); if (!project) { throw new Error(`Project '${projectName}' not found`); } const projectPath = path.join(CONFIG.RAG.PROJECTS_DIR, projectName); const chunksPath = path.join(projectPath, 'chunks'); const vectorsPath = path.join(projectPath, 'vectors.bin'); const metadataPath = path.join(projectPath, 'metadata.json'); // Get book metadata const idQuery = `id:${bookIds.join(' OR id:')}`; const listResult = await this.runCalibreCommand([ 'list', '--fields', 'id,title,authors,formats', '--for-machine', '--search', idQuery ]); const books = JSON.parse(listResult || '[]'); const allChunks = []; const allVectors = []; const allMetadata = []; for (const book of books) { this.log(`Processing book: ${book.title}`); let content = ''; let contentSource = 'unknown'; // Try text format first const txtPath = book.formats?.find(f => f.endsWith('.txt')); if (txtPath && fs.existsSync(txtPath)) { this.log(`Using text format: ${txtPath}`); content = fs.readFileSync(txtPath, 'utf8'); contentSource = 'text'; } else { // Fallback to OCR this.log(`No text format available for: ${book.title}, trying OCR...`); content = await this.processBookWithOCR(book); contentSource = 'ocr'; if (!content || content.trim().length === 0) { this.log(`OCR failed or no content extracted for: ${book.title}`); continue; } } // Read and chunk content const bookMetadata = { book_id: book.id, title: book.title, authors: book.authors, project: projectName, content_source: contentSource, content_length: content.length }; const chunks = this.intelligentChunk(content, bookMetadata); // Generate embeddings for chunks for (const chunk of chunks) { try { const embedding = await this.generateEmbedding(chunk.text); allChunks.push(chunk); allVectors.push(embedding); allMetadata.push(chunk.metadata); // Save individual chunk fs.writeFileSync( path.join(chunksPath, `${chunk.id}.json`), JSON.stringify(chunk, null, 2) ); this.log(`Processed chunk ${chunk.id} from ${book.title}`); } catch (error) { this.log(`Failed to process chunk ${chunk.id}: ${error.message}`); } } } // Save vectors and metadata if (allVectors.length > 0) { this.saveVectors(vectorsPath, allVectors); fs.writeFileSync(metadataPath, JSON.stringify(allMetadata, null, 2)); // Update project config project.books = [...new Set([...project.books, ...bookIds])]; project.chunk_count = allChunks.length; project.last_updated = new Date().toISOString(); fs.writeFileSync( path.join(projectPath, 'project.json'), JSON.stringify(project, null, 2) ); this.projects.set(projectName, project); } return { processed_books: books.length, total_chunks: allChunks.length, project: project }; }
  • Input schema definition for the add_books_to_project tool, specifying project_name as string and book_ids as array of integers.
    { name: 'add_books_to_project', description: 'Add books to a RAG project for vectorization and context search', inputSchema: { type: 'object', properties: { project_name: { type: 'string', description: 'Name of the project' }, book_ids: { type: 'array', items: { type: 'integer' }, description: 'Array of book IDs to add to the project' } }, required: ['project_name', 'book_ids'] } },
  • server.js:1165-1186 (registration)
    Tool call handler registration in the switch statement within handleToolsCall method, which validates arguments and invokes the addBooksToProject handler.
    case 'add_books_to_project': const projName = args.project_name; const bookIds = args.book_ids; if (!projName || !bookIds) { this.sendError(id, -32602, 'Missing required parameters: project_name, book_ids'); return; } try { const result = await this.addBooksToProject(projName, bookIds); this.sendSuccess(id, { content: [{ type: 'text', text: `Successfully processed ${result.processed_books} books and created ${result.total_chunks} chunks for project '${projName}'.\n\nProject is now ready for context search!` }], result: result }); } catch (error) { this.sendError(id, -32603, error.message); } break;

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