UNPKG

@creedspace/mcp-server

Version:

Universal MCP server for Creed Space - AI safety guardrails in 10 seconds

215 lines 8.74 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.CreedSpaceClient = void 0; const fetch_polyfill_js_1 = require("./fetch-polyfill.js"); class CreedSpaceClient { config; cache = new Map(); constructor(config = {}) { this.config = { apiUrl: config.apiUrl || process.env.CREEDSPACE_API_URL || 'https://api.creed.space', apiKey: config.apiKey || process.env.CREEDSPACE_API_KEY, persona: config.persona || 'ambassador', cacheEnabled: config.cacheEnabled ?? true, cacheTtl: config.cacheTtl || 300000, // 5 minutes offlineMode: config.offlineMode || false, }; } async fetchWithCache(endpoint) { const cacheKey = `${endpoint}`; if (this.config.cacheEnabled) { const cached = this.cache.get(cacheKey); if (cached && Date.now() - cached.timestamp < this.config.cacheTtl) { return cached.data; } } try { const headers = { 'Content-Type': 'application/json', 'User-Agent': '@creedspace/mcp-server/1.0.0', }; if (this.config.apiKey) { headers['X-API-Key'] = this.config.apiKey; } const response = await (0, fetch_polyfill_js_1.fetch)(`${this.config.apiUrl}${endpoint}`, { headers }); if (!response.ok) { throw new Error(`API request failed: ${response.status} ${response.statusText}`); } const data = await response.json(); if (this.config.cacheEnabled) { this.cache.set(cacheKey, { data, timestamp: Date.now() }); } return data; } catch (error) { // SECURITY: Log API failures with context for monitoring const errorContext = { endpoint, apiUrl: this.config.apiUrl, hasApiKey: !!this.config.apiKey, cacheEnabled: this.config.cacheEnabled, timestamp: new Date().toISOString() }; console.error('[API_CLIENT_ERROR]', JSON.stringify(errorContext)); // If offline mode is enabled and we have cached data, return it if (this.config.offlineMode && this.cache.has(cacheKey)) { console.warn('[FALLBACK_MODE] API unavailable, using cached data', { endpoint, cacheAge: Date.now() - (this.cache.get(cacheKey)?.timestamp || 0) }); return this.cache.get(cacheKey)?.data; } // Re-throw with enhanced context if (error instanceof Error) { const enhancedError = new Error(`API request to ${endpoint} failed: ${error.message}`); enhancedError.cause = error; throw enhancedError; } throw error; } } async getPersonas() { const data = await this.fetchWithCache('/api/personas/list'); return data.personas || []; } async getPersona(personaId) { const personas = await this.getPersonas(); const persona = personas.find((p) => p.id === personaId); if (!persona) { throw new Error(`Persona not found: ${personaId}`); } return persona; } async getConstitutions(personaId) { // For now, return default constitutions based on persona // This would normally call the API endpoint const defaultConstitutions = { ambassador: [ { id: 'uef', name: 'Universal Ethical Framework', content: 'Prioritize human safety and wellbeing', isSystemConstitution: true, }, ], nanny: [ { id: 'child-safety', name: 'Child Safety Constitution', content: 'Ensure child-appropriate content and safety', isSystemConstitution: true, }, ], sentinel: [ { id: 'privacy', name: 'Privacy Protection', content: 'Protect user privacy and data security', isSystemConstitution: true, }, ], }; return defaultConstitutions[personaId || 'ambassador'] || defaultConstitutions['ambassador']; } async getMergedConstitution(personaId) { // For now, return a simulated merged constitution // This would normally call the API endpoint const persona = await this.getPersona(personaId); const constitutions = await this.getConstitutions(personaId); return { persona, constitutions, mergedContent: constitutions.map((c) => c.content).join('\n\n'), totalRules: constitutions.length, }; } async getUvcQualities(personaId) { try { const data = await this.fetchWithCache(`/api/uvc/${personaId}`); return data; } catch (error) { // SECURITY: Log UVC fetch failures for monitoring console.warn('[UVC_FETCH_ERROR]', { personaId, error: error instanceof Error ? error.message : String(error), timestamp: new Date().toISOString() }); // UVC might not be available for all personas - this is expected // Re-throw critical errors but return null for 404/not found if (error instanceof Error && error.message.includes('404')) { return null; // Expected: UVC not configured for this persona } // For unexpected errors, preserve the error chain if (error instanceof Error) { const enhancedError = new Error(`Failed to fetch UVC qualities for persona ${personaId}: ${error.message}`); enhancedError.cause = error; throw enhancedError; } throw new Error(`Failed to fetch UVC qualities for persona ${personaId}: ${String(error)}`); } } async getExportPreview(config) { const response = await (0, fetch_polyfill_js_1.fetch)(`${this.config.apiUrl}/api/export/preview`, { method: 'POST', headers: { 'Content-Type': 'application/json', ...(this.config.apiKey && { 'X-API-Key': this.config.apiKey }), }, body: JSON.stringify(config), }); if (!response.ok) { throw new Error(`Export preview failed: ${response.status}`); } const data = (await response.json()); // Validate response structure if (!data || typeof data !== 'object') { throw new Error('Invalid response format from export preview API'); } if ('preview' in data && typeof data.preview === 'string') { return data.preview; } else if ('data' in data && data.data && typeof data.data === 'object' && 'preview' in data.data) { return data.data.preview; } else { throw new Error('Invalid export preview response format'); } } async getSystemPrompt(personaId) { const merged = await this.getMergedConstitution(personaId); // Build a comprehensive system prompt const parts = [ `You are operating with the ${merged.persona.name} persona.`, '', '# Constitution Rules', merged.mergedContent, '', ]; // Add UVC qualities if available const uvc = await this.getUvcQualities(personaId); if (uvc?.qualities) { parts.push('# Value Alignment'); if (uvc.qualities.desired?.length) { parts.push(`Desired: ${uvc.qualities.desired.join(', ')}`); } if (uvc.qualities.disliked?.length) { parts.push(`Disliked: ${uvc.qualities.disliked.join(', ')}`); } if (uvc.qualities.never?.length) { parts.push(`Never: ${uvc.qualities.never.join(', ')}`); } parts.push(''); } return parts.join('\n'); } clearCache() { this.cache.clear(); } setPersona(personaId) { this.config.persona = personaId; this.clearCache(); // Clear cache when persona changes } getCurrentPersona() { return this.config.persona || 'ambassador'; } } exports.CreedSpaceClient = CreedSpaceClient; //# sourceMappingURL=api-client.js.map