@spaik/mcp-server-roi
Version:
MCP server for AI ROI prediction and tracking with Monte Carlo simulations
136 lines • 5.64 kB
JavaScript
import { createClient } from '@supabase/supabase-js';
import * as dotenv from 'dotenv';
import { logger } from '../utils/logger.js';
import { DatabaseError, ConfigurationError } from '../utils/errors.js';
dotenv.config();
// Validate environment variables
if (!process.env.SUPABASE_URL || !process.env.SUPABASE_ANON_KEY) {
const error = new ConfigurationError('Missing Supabase environment variables. Please set SUPABASE_URL and SUPABASE_ANON_KEY', {
hasUrl: !!process.env.SUPABASE_URL,
hasAnonKey: !!process.env.SUPABASE_ANON_KEY
});
logger.error('Failed to initialize Supabase client', error);
throw error;
}
logger.info('Initializing Supabase clients', {
hasServiceKey: !!process.env.SUPABASE_SERVICE_KEY
});
// Public client for user operations (with RLS)
export const supabase = createClient(process.env.SUPABASE_URL, process.env.SUPABASE_ANON_KEY, {
auth: {
persistSession: false
}
});
// Admin client for server-side operations (internal use only)
const supabaseAdmin = process.env.SUPABASE_SERVICE_KEY
? createClient(process.env.SUPABASE_URL, process.env.SUPABASE_SERVICE_KEY, {
auth: {
persistSession: false
}
})
: null;
if (!supabaseAdmin) {
logger.warn('Admin client not available - some features may be limited');
}
// Data access layer - all admin operations should go through these functions
export const dataAccess = {
// System-level operations that bypass RLS (use with caution)
async systemQuery(query) {
if (!supabaseAdmin) {
throw new ConfigurationError('Admin client not available - service key required for system operations', { operation: 'systemQuery' });
}
try {
logger.debug('Executing system query');
const result = await query();
logger.debug('System query completed successfully');
return result;
}
catch (error) {
logger.error('System query failed', error);
throw new DatabaseError(`System query failed: ${error.message}`, { originalError: error.message });
}
},
// Migrate existing data to include user_id
async migrateProjectOwnership(projectId, userId) {
if (!supabaseAdmin) {
throw new ConfigurationError('Admin client required for migration', { operation: 'migrateProjectOwnership' });
}
logger.info('Migrating project ownership', { projectId, userId });
try {
const { error } = await supabaseAdmin
.from('projects')
.update({ user_id: userId })
.eq('id', projectId);
if (error) {
throw new DatabaseError(`Failed to migrate project ownership: ${error.message}`, { projectId, userId, code: error.code });
}
logger.info('Project ownership migrated successfully', { projectId, userId });
}
catch (error) {
if (error instanceof DatabaseError)
throw error;
logger.error('Unexpected error during migration', error);
throw new DatabaseError(`Migration failed: ${error.message}`, { projectId, userId });
}
},
// System health checks
async checkDatabaseHealth() {
const client = supabaseAdmin || supabase;
try {
logger.debug('Checking database health');
const { error } = await client
.from('projects')
.select('count')
.limit(0);
if (error) {
logger.error('Database health check failed', error);
return false;
}
logger.debug('Database health check passed');
return true;
}
catch (error) {
logger.error('Unexpected error during health check', error);
return false;
}
},
// Get anonymous statistics (no user data)
async getSystemStats() {
if (!supabaseAdmin) {
throw new ConfigurationError('Admin client required for system stats', { operation: 'getSystemStats' });
}
logger.debug('Fetching system statistics');
try {
const { data, error } = await supabaseAdmin
.from('projects')
.select('industry, status, created_at');
if (error) {
throw new DatabaseError(`Failed to fetch system stats: ${error.message}`, { code: error.code });
}
// Return anonymized statistics only
const stats = {
totalProjects: data?.length || 0,
byIndustry: data?.reduce((acc, p) => {
acc[p.industry] = (acc[p.industry] || 0) + 1;
return acc;
}, {}) || {},
byStatus: data?.reduce((acc, p) => {
acc[p.status] = (acc[p.status] || 0) + 1;
return acc;
}, {}) || {}
};
logger.debug('System statistics fetched', { totalProjects: stats.totalProjects });
return stats;
}
catch (error) {
if (error instanceof DatabaseError)
throw error;
logger.error('Unexpected error fetching stats', error);
throw new DatabaseError(`Failed to get system stats: ${error.message}`, {});
}
}
};
// Export the appropriate client for MCP server operations
// MCP servers run server-side and should use the admin client when available
export const mcpDb = supabaseAdmin || supabase;
//# sourceMappingURL=supabase.js.map