@creedspace/mcp-server
Version:
Universal MCP server for Creed Space - AI safety guardrails in 10 seconds
215 lines • 8.74 kB
JavaScript
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
;