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 context search, enabling semantic search and retrieval from your Calibre ebook library.

Instructions

Add books to a RAG project for vectorization and context search

Input Schema

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

Implementation Reference

  • The core handler function that processes books for a RAG project: fetches metadata, extracts content (txt or OCR), intelligently chunks it, generates embeddings, saves vectors and metadata, and updates project config.
    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
        };
    }
  • JSON schema defining the input parameters for the 'add_books_to_project' tool: project_name (string) and book_ids (array of integers).
    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)
    Registration and dispatch logic in the tools/call handler: validates arguments, calls the addBooksToProject method, and sends success/error responses.
    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;
  • server.js:1030-1048 (registration)
    Tool registration in the tools/list response, including name, description, and input schema.
    {
        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']
        }
    },

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