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
JavaScript
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