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
| Name | Required | Description | Default |
|---|---|---|---|
| book_ids | Yes | Array of book IDs to add to the project | |
| project_name | Yes | Name of the project |
Implementation Reference
- server.js:604-710 (handler)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 }; }
- server.js:1030-1048 (schema)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;