UNPKG

dbx-mcp-server

Version:

A Model Context Protocol server for Dropbox integration with AI-powered PDF analysis, multi-directory indexing, and parallel processing

557 lines 24.5 kB
import * as fs from 'fs'; import * as path from 'path'; import { log } from './config.js'; export class SchemaManager { constructor(configPath) { this.configPath = configPath || path.join(process.cwd(), 'schema-config.json'); this.loadConfiguration(); } loadConfiguration() { try { if (fs.existsSync(this.configPath)) { const configData = fs.readFileSync(this.configPath, 'utf8'); this.config = JSON.parse(configData); log.info('Schema configuration loaded', { path: this.configPath, version: this.config.version, documentTypes: Object.keys(this.config.documentTypes).length }); } else { log.info('No schema configuration found, creating default configuration', { path: this.configPath }); this.config = this.getDefaultConfiguration(); this.saveConfiguration(); } this.validateConfiguration(); } catch (error) { log.error('Failed to load schema configuration', { path: this.configPath, error: error instanceof Error ? error.message : String(error) }); throw new Error(`Failed to load schema configuration: ${error instanceof Error ? error.message : String(error)}`); } } validateConfiguration() { if (!this.config.version || !this.config.documentTypes) { throw new Error('Invalid schema configuration: missing required fields'); } // Validate each document type for (const [typeName, typeConfig] of Object.entries(this.config.documentTypes)) { if (!typeConfig.name || !typeConfig.fields) { throw new Error(`Invalid document type configuration: ${typeName}`); } // Validate fields for (const [fieldName, fieldConfig] of Object.entries(typeConfig.fields)) { if (!fieldConfig.type) { throw new Error(`Invalid field configuration: ${typeName}.${fieldName} - missing type`); } } } } saveConfiguration() { try { const configData = JSON.stringify(this.config, null, 2); fs.writeFileSync(this.configPath, configData, 'utf8'); log.info('Schema configuration saved', { path: this.configPath }); } catch (error) { log.error('Failed to save schema configuration', { path: this.configPath, error: error instanceof Error ? error.message : String(error) }); throw new Error(`Failed to save schema configuration: ${error instanceof Error ? error.message : String(error)}`); } } getConfiguration() { return JSON.parse(JSON.stringify(this.config)); // Deep copy } getDocumentTypes() { return Object.keys(this.config.documentTypes).filter(typeName => this.config.documentTypes[typeName].enabled); } getDocumentTypeConfig(typeName) { return this.config.documentTypes[typeName] || null; } generateJSONSchema() { const enabledTypes = this.getDocumentTypes(); // Build properties object with all fields from all document types const allFields = {}; for (const typeName of enabledTypes) { const typeConfig = this.config.documentTypes[typeName]; for (const [fieldName, fieldConfig] of Object.entries(typeConfig.fields)) { allFields[fieldName] = this.convertFieldConfigToJSONSchema(fieldConfig); } } return { name: "document_analysis", strict: true, schema: { type: "object", properties: { doc_type: { type: "string", enum: enabledTypes, description: "Type of document analyzed" }, synopsis: { type: "string", description: "Brief 2-3 sentence summary of the document content" }, confidence_score: { type: "number", minimum: 0, maximum: 1, description: "Confidence level of the analysis from 0.0 to 1.0" }, fields: { type: "object", description: "Type-specific extracted fields based on document type", properties: allFields, additionalProperties: false } }, required: ["doc_type", "synopsis", "confidence_score", "fields"], additionalProperties: false } }; } convertFieldConfigToJSONSchema(fieldConfig) { const schema = { type: fieldConfig.required ? fieldConfig.type : [fieldConfig.type, "null"], description: fieldConfig.description }; if (fieldConfig.enum) { schema.enum = fieldConfig.enum; } if (fieldConfig.minimum !== undefined) { schema.minimum = fieldConfig.minimum; } if (fieldConfig.maximum !== undefined) { schema.maximum = fieldConfig.maximum; } if (fieldConfig.type === 'array' && fieldConfig.items) { schema.items = this.convertFieldConfigToJSONSchema(fieldConfig.items); } if (fieldConfig.type === 'object' && fieldConfig.properties) { schema.properties = {}; for (const [propName, propConfig] of Object.entries(fieldConfig.properties)) { schema.properties[propName] = this.convertFieldConfigToJSONSchema(propConfig); } } return schema; } generateAnalysisPrompt() { const enabledTypes = this.getDocumentTypes(); let prompt = this.config.globalPrompt.basePrompt + '\n\n'; // Add classification section prompt += this.config.globalPrompt.classification + '\n'; // Add document type descriptions for (const typeName of enabledTypes) { const typeConfig = this.config.documentTypes[typeName]; prompt += `- **${typeName}**: ${typeConfig.description}\n`; } prompt += '\n'; // Add extraction instructions for each type prompt += 'Extract all relevant information based on the document type:\n\n'; for (const typeName of enabledTypes) { const typeConfig = this.config.documentTypes[typeName]; if (typeConfig.prompt?.extraction) { prompt += `**For ${typeName}**: ${typeConfig.prompt.extraction}\n`; } else { // Generate default extraction instruction const fieldNames = Object.keys(typeConfig.fields).slice(0, 5).join(', '); prompt += `**For ${typeName}**: Extract ${fieldNames} and other relevant fields\n`; } } prompt += '\n'; // Add global guidelines prompt += 'Guidelines:\n'; for (const guideline of this.config.globalPrompt.guidelines) { prompt += `- ${guideline}\n`; } // Add type-specific guidelines for (const typeName of enabledTypes) { const typeConfig = this.config.documentTypes[typeName]; if (typeConfig.prompt?.guidelines && typeConfig.prompt.guidelines.length > 0) { prompt += `\n**${typeName} specific guidelines:**\n`; for (const guideline of typeConfig.prompt.guidelines) { prompt += `- ${guideline}\n`; } } } prompt += '\nThe response will be automatically structured according to the required format.\n'; return prompt; } generateDatabaseSchema() { const queries = []; // Generate table creation for each document type for (const [typeName, typeConfig] of Object.entries(this.config.documentTypes)) { if (!typeConfig.enabled) continue; const tableName = this.getTableName(typeName); let createQuery = `CREATE TABLE IF NOT EXISTS ${tableName} (\n`; createQuery += ' id INTEGER PRIMARY KEY AUTOINCREMENT,\n'; createQuery += ' document_id INTEGER NOT NULL REFERENCES documents(id) ON DELETE CASCADE,\n'; // Add fields for (const [fieldName, fieldConfig] of Object.entries(typeConfig.fields)) { if (fieldConfig.type === 'array' || fieldConfig.type === 'object') { // Store complex types as JSON text createQuery += ` ${fieldName} TEXT,\n`; } else { const sqlType = this.getSQLType(fieldConfig.type); createQuery += ` ${fieldName} ${sqlType},\n`; } } createQuery += ' created_at TEXT DEFAULT CURRENT_TIMESTAMP\n'; createQuery += ')'; queries.push(createQuery); // Create index for foreign key queries.push(`CREATE INDEX IF NOT EXISTS idx_${tableName}_document_id ON ${tableName}(document_id)`); } return queries; } getSQLType(fieldType) { switch (fieldType) { case 'string': return 'TEXT'; case 'number': return 'REAL'; case 'boolean': return 'BOOLEAN'; default: return 'TEXT'; } } getTableName(docType) { return docType === 'contract' ? 'contracts' : docType === 'invoice' ? 'invoices' : docType === 'proposal' ? 'proposals' : docType === 'report' ? 'reports' : `${docType}s`; } // Management methods addDocumentType(config) { if (this.config.documentTypes[config.name]) { throw new Error(`Document type '${config.name}' already exists`); } this.config.documentTypes[config.name] = config; this.saveConfiguration(); log.info('Document type added', { typeName: config.name }); } updateDocumentType(typeName, updates) { if (!this.config.documentTypes[typeName]) { throw new Error(`Document type '${typeName}' not found`); } this.config.documentTypes[typeName] = { ...this.config.documentTypes[typeName], ...updates }; this.saveConfiguration(); log.info('Document type updated', { typeName }); } removeDocumentType(typeName) { if (!this.config.documentTypes[typeName]) { throw new Error(`Document type '${typeName}' not found`); } delete this.config.documentTypes[typeName]; this.saveConfiguration(); log.info('Document type removed', { typeName }); } updateGlobalPrompt(updates) { this.config.globalPrompt = { ...this.config.globalPrompt, ...updates }; this.saveConfiguration(); log.info('Global prompt updated'); } getDefaultConfiguration() { return { version: "1.0.0", globalPrompt: { basePrompt: "Analyze this PDF document and extract structured information.", classification: "Classify the document as one of these types:", guidelines: [ "Use null for any missing or unclear information", "Format dates as YYYY-MM-DD", "Include confidence score based on document clarity and completeness", "Provide a clear synopsis summarizing the document purpose", "Extract numerical values accurately (amounts, quantities, etc.)" ] }, documentTypes: { contract: { name: "contract", description: "Legal agreements, service contracts, employment contracts", enabled: true, fields: { contract_number: { type: "string", description: "Contract identification number" }, contract_type: { type: "string", description: "Type of contract" }, effective_date: { type: "string", description: "Contract effective date (YYYY-MM-DD)" }, expiration_date: { type: "string", description: "Contract expiration date (YYYY-MM-DD)" }, auto_renewal: { type: "boolean", description: "Whether contract auto-renews" }, notice_period_days: { type: "number", description: "Notice period in days" }, total_value: { type: "number", description: "Total contract value" }, currency: { type: "string", description: "Currency code" }, payment_terms: { type: "string", description: "Payment terms description" }, governing_law: { type: "string", description: "Governing law jurisdiction" }, parties: { type: "array", items: { type: "string" }, description: "List of contracting parties" } }, prompt: { extraction: "Extract contract details, parties, dates, values, terms", guidelines: [ "Focus on legal entities and their roles", "Extract monetary values with currency", "Pay attention to renewal and termination clauses" ] } }, invoice: { name: "invoice", description: "Bills, invoices, receipts, payment requests", enabled: true, fields: { invoice_number: { type: "string", description: "Invoice number" }, invoice_date: { type: "string", description: "Invoice date (YYYY-MM-DD)" }, due_date: { type: "string", description: "Payment due date (YYYY-MM-DD)" }, supplier_name: { type: "string", description: "Supplier/vendor name" }, supplier_address: { type: "string", description: "Supplier address" }, customer_name: { type: "string", description: "Customer/client name" }, customer_address: { type: "string", description: "Customer address" }, subtotal: { type: "number", description: "Subtotal amount" }, tax_amount: { type: "number", description: "Tax amount" }, total_amount: { type: "number", description: "Total amount" }, currency: { type: "string", description: "Currency code" }, payment_status: { type: "string", description: "Payment status" }, payment_method: { type: "string", description: "Payment method" }, items: { type: "array", items: { type: "object", properties: { description: { type: "string", description: "Item description" }, quantity: { type: "number", description: "Item quantity" }, unit_price: { type: "number", description: "Price per unit" }, total_price: { type: "number", description: "Total price for item" } } }, description: "List of invoice line items" } }, prompt: { extraction: "Extract billing information, line items, amounts, dates", guidelines: [ "Extract all line items with quantities and prices", "Calculate totals and verify against stated amounts", "Identify payment terms and due dates clearly" ] } }, proposal: { name: "proposal", description: "Business proposals, project proposals, quotes", enabled: true, fields: { proposal_number: { type: "string", description: "Proposal number" }, proposal_date: { type: "string", description: "Proposal date (YYYY-MM-DD)" }, client_name: { type: "string", description: "Client name" }, project_title: { type: "string", description: "Project title" }, total_value: { type: "number", description: "Total proposal value" }, currency: { type: "string", description: "Currency code" }, validity_period: { type: "string", description: "Proposal validity period" }, deliverables: { type: "array", items: { type: "string" }, description: "List of project deliverables" }, timeline: { type: "string", description: "Project timeline" } }, prompt: { extraction: "Extract project details, deliverables, timeline, client info", guidelines: [ "Focus on scope of work and deliverables", "Extract timeline and milestone information", "Identify pricing structure and terms" ] } }, report: { name: "report", description: "Research reports, financial reports, analysis documents", enabled: true, fields: { report_title: { type: "string", description: "Report title" }, report_date: { type: "string", description: "Report date (YYYY-MM-DD)" }, report_type: { type: "string", description: "Type of report" }, author: { type: "string", description: "Report author" }, period_covered: { type: "string", description: "Period covered by report" }, key_findings: { type: "array", items: { type: "string" }, description: "Key findings from the report" }, recommendations: { type: "array", items: { type: "string" }, description: "Recommendations from the report" } }, prompt: { extraction: "Extract findings, recommendations, analysis, metadata", guidelines: [ "Summarize key findings and conclusions", "Extract actionable recommendations", "Identify data sources and methodologies" ] } } } }; } } // Export singleton instance with error handling let schemaManager; try { schemaManager = new SchemaManager(); } catch (error) { log.error('Failed to initialize schema manager, using fallback instance', { error: error instanceof Error ? error.message : String(error) }); // Create a fallback instance that won't fail the entire server schemaManager = { getConfiguration() { return { version: "1.0.0", globalPrompt: { basePrompt: "Analyze this PDF document and extract structured information.", classification: "Classify the document as one of these types:", guidelines: ["Use null for any missing or unclear information"] }, documentTypes: {} }; }, getDocumentTypes() { return []; }, getDocumentTypeConfig() { return null; }, generateJSONSchema() { return {}; }, generateAnalysisPrompt() { return "Default analysis prompt"; }, generateDatabaseSchema() { return []; }, saveConfiguration() { return; }, addDocumentType() { throw new Error("Schema manager not properly initialized"); }, updateDocumentType() { throw new Error("Schema manager not properly initialized"); }, removeDocumentType() { throw new Error("Schema manager not properly initialized"); }, updateGlobalPrompt() { throw new Error("Schema manager not properly initialized"); } }; } export { schemaManager }; //# sourceMappingURL=schema-manager.js.map