Skip to main content
Glama
jakedx6
by jakedx6
api-client.js16.3 kB
import { logger } from './logger.js'; // Error classes export class HeliosError extends Error { constructor(message, code = 'UNKNOWN_ERROR', statusCode = 500, originalError) { super(message); this.message = message; this.code = code; this.statusCode = statusCode; this.originalError = originalError; this.name = 'HeliosError'; } } export class NotFoundError extends HeliosError { constructor(resource, id) { super(`${resource}${id ? ` with ID ${id}` : ''} not found`, 'NOT_FOUND', 404); } } export class UnauthorizedError extends HeliosError { constructor(message = 'Unauthorized') { super(message, 'UNAUTHORIZED', 401); } } export class ValidationError extends HeliosError { constructor(message, field) { super(message, 'VALIDATION_ERROR', 400); if (field) { this.message = `${field}: ${message}`; } } } export class ApiClient { constructor() { this.currentUserId = null; this.currentTenantId = null; const baseUrl = process.env.HELIOS_API_URL; const apiKey = process.env.HELIOS_API_KEY; // Provide detailed error information if (!baseUrl) { logger.error('HELIOS_API_URL environment variable is not set'); throw new Error('Missing HELIOS_API_URL environment variable. This should be set in your MCP client configuration (e.g., Claude Desktop config).'); } if (!apiKey) { logger.error('HELIOS_API_KEY environment variable is not set'); throw new Error('Missing HELIOS_API_KEY environment variable. This should be set in your MCP client configuration (e.g., Claude Desktop config).'); } this.baseUrl = baseUrl.replace(/\/$/, ''); // Remove trailing slash this.apiKey = apiKey; logger.info('Helios API client initialized', { baseUrl: this.baseUrl, keyPrefix: this.apiKey.substring(0, 16) + '...', // Log partial key for debugging keyLength: this.apiKey.length, envBaseUrl: process.env.HELIOS_API_URL, envKeyLength: process.env.HELIOS_API_KEY?.length }); } /** * Make authenticated API request */ async request(endpoint, options = {}) { const url = `${this.baseUrl}${endpoint}`; const config = { ...options, headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${this.apiKey}`, 'X-MCP-Client': 'helios9-mcp-server', ...options.headers, }, }; try { const headers = config.headers; logger.info(`API Request: ${config.method || 'GET'} ${url}`, { hasAuth: !!headers?.['Authorization'], authPrefix: headers?.['Authorization']?.substring(0, 20) + '...' }); const response = await fetch(url, config); logger.info(`API Response: ${response.status} ${response.statusText}`, { url, ok: response.ok }); if (!response.ok) { const errorText = await response.text(); let errorData; try { errorData = JSON.parse(errorText); } catch { errorData = { message: errorText }; } logger.error(`API Error: ${response.status} ${response.statusText}`, { url, error: errorData }); switch (response.status) { case 401: throw new UnauthorizedError(errorData.message || 'Invalid API key'); case 404: throw new NotFoundError('Resource'); case 400: throw new ValidationError(errorData.message || 'Validation failed'); default: throw new HeliosError(errorData.message || `API request failed: ${response.status}`, 'API_ERROR', response.status, errorData); } } const data = await response.json(); return data; } catch (error) { if (error instanceof HeliosError) { throw error; } logger.error(`API Request failed: ${url}`, error); throw new HeliosError(`Network error: ${error instanceof Error ? error.message : 'Unknown error'}`, 'NETWORK_ERROR', 500, error); } } /** * Authenticate with API key (validates the key and gets user info) */ async authenticate() { try { const response = await this.request('/api/auth/validate', { method: 'POST', }); this.currentUserId = response.user.id; this.currentTenantId = response.user.tenant_id || null; logger.info(`API authenticated for user: ${response.user.email}, tenant: ${response.user.tenant_id || 'none'}`); return response.user; } catch (error) { logger.error('API authentication failed:', error); throw error instanceof HeliosError ? error : new UnauthorizedError(); } } /** * Get current user ID */ getCurrentUserId() { if (!this.currentUserId) { throw new UnauthorizedError('No authenticated user'); } return this.currentUserId; } /** * Get current tenant ID */ getCurrentTenantId() { return this.currentTenantId; } /** * Project operations */ async getProjects(filter, pagination, sort) { const params = new URLSearchParams(); if (filter?.status) params.append('status', filter.status); if (filter?.search) params.append('search', filter.search); if (filter?.created_after) params.append('created_after', filter.created_after); if (filter?.created_before) params.append('created_before', filter.created_before); if (pagination?.limit) params.append('limit', pagination.limit.toString()); if (pagination?.offset) params.append('offset', pagination.offset.toString()); if (sort?.field) params.append('sort_field', sort.field); if (sort?.order) params.append('sort_order', sort.order); const queryString = params.toString(); const endpoint = `/api/mcp/projects${queryString ? `?${queryString}` : ''}`; const response = await this.request(endpoint); return response.projects; } async getProject(projectId) { const response = await this.request(`/api/mcp/projects/${projectId}`); return response.project; } async createProject(projectData) { const response = await this.request('/api/mcp/projects', { method: 'POST', body: JSON.stringify(projectData), }); logger.info(`Project created: ${response.project.name} (${response.project.id})`); return response.project; } async updateProject(projectId, updates) { const response = await this.request(`/api/mcp/projects/${projectId}`, { method: 'PATCH', body: JSON.stringify(updates), }); return response.project; } /** * Task operations */ async getTasks(filter, pagination, sort) { const params = new URLSearchParams(); if (filter?.project_id) params.append('project_id', filter.project_id); if (filter?.status) params.append('status', filter.status); if (filter?.assignee_id) params.append('assignee_id', filter.assignee_id); if (filter?.search) params.append('search', filter.search); if (pagination?.limit) params.append('limit', pagination.limit.toString()); if (pagination?.offset) params.append('offset', pagination.offset.toString()); if (sort?.field) params.append('sort_field', sort.field); if (sort?.order) params.append('sort_order', sort.order); const queryString = params.toString(); const endpoint = `/api/mcp/tasks${queryString ? `?${queryString}` : ''}`; const response = await this.request(endpoint); return response.tasks; } async getTask(taskId) { const response = await this.request(`/api/mcp/tasks/${taskId}`); return response.task; } async createTask(taskData) { const response = await this.request('/api/mcp/tasks', { method: 'POST', body: JSON.stringify(taskData), }); return response.task; } async updateTask(taskId, updates) { const response = await this.request(`/api/mcp/tasks/${taskId}`, { method: 'PATCH', body: JSON.stringify(updates), }); return response.task; } /** * Document operations */ async getDocuments(filter, pagination, sort) { const params = new URLSearchParams(); if (filter?.project_id) params.append('project_id', filter.project_id); if (filter?.type) params.append('document_type', filter.type); if (filter?.search) params.append('search', filter.search); if (pagination?.limit) params.append('limit', pagination.limit.toString()); if (pagination?.offset) params.append('offset', pagination.offset.toString()); if (sort?.field) params.append('sort_field', sort.field); if (sort?.order) params.append('sort_order', sort.order); const queryString = params.toString(); const endpoint = `/api/mcp/documents${queryString ? `?${queryString}` : ''}`; const response = await this.request(endpoint); return response.documents; } async getDocument(documentId) { const response = await this.request(`/api/mcp/documents/${documentId}`); return response.document; } async createDocument(documentData) { const response = await this.request('/api/mcp/documents', { method: 'POST', body: JSON.stringify(documentData), }); return response.document; } async updateDocument(documentId, updates) { const response = await this.request(`/api/mcp/documents/${documentId}`, { method: 'PATCH', body: JSON.stringify(updates), }); return response.document; } /** * Get comprehensive project context for AI agents */ async getProjectContext(projectId) { const response = await this.request(`/api/mcp/projects/${projectId}/context`); return response.context; } /** * Initiative operations */ async getInitiatives(filter, pagination, sort) { const params = new URLSearchParams(); if (filter?.project_id) params.append('project_id', filter.project_id); if (filter?.status) params.append('status', filter.status); if (filter?.priority) params.append('priority', filter.priority); if (filter?.search) params.append('search', filter.search); if (pagination?.limit) params.append('limit', pagination.limit.toString()); if (pagination?.offset) params.append('offset', pagination.offset.toString()); if (sort?.field) params.append('sort_field', sort.field); if (sort?.order) params.append('sort_order', sort.order); const queryString = params.toString(); const endpoint = `/api/mcp/initiatives${queryString ? `?${queryString}` : ''}`; const response = await this.request(endpoint); return response.initiatives; } async getInitiative(initiativeId) { const response = await this.request(`/api/mcp/initiatives/${initiativeId}`); return response.initiative; } async createInitiative(initiativeData) { const response = await this.request('/api/mcp/initiatives', { method: 'POST', body: JSON.stringify(initiativeData), }); logger.info(`Initiative created: ${response.initiative.name} (${response.initiative.id})`); return response.initiative; } async updateInitiative(initiativeId, updates) { const response = await this.request(`/api/mcp/initiatives/${initiativeId}`, { method: 'PATCH', body: JSON.stringify(updates), }); return response.initiative; } async getInitiativeContext(initiativeId) { const response = await this.request(`/api/mcp/initiatives/${initiativeId}/context`); return response.context; } async getInitiativeInsights(initiativeId) { const response = await this.request(`/api/mcp/initiatives/${initiativeId}/insights`); return response.insights; } async searchWorkspace(query, filters, limit) { const response = await this.request('/api/mcp/search', { method: 'POST', body: JSON.stringify({ query, filters, limit }), }); return response; } async getEnhancedProjectContext(projectId) { const response = await this.request(`/api/mcp/projects/${projectId}/context-enhanced`); return response.context; } async getWorkspaceContext() { const response = await this.request('/api/mcp/workspace/context'); return response.context; } /** * Additional methods for MCP compatibility */ async updateTasksByProject(projectId, updates) { await this.request(`/api/mcp/projects/${projectId}/tasks`, { method: 'PATCH', body: JSON.stringify(updates), }); } // Placeholder methods for missing functionality async createTaskDependency(dependency) { logger.warn('Task dependencies not yet implemented in API'); return { id: 'placeholder', ...dependency }; } async getTaskDependencies(taskId) { logger.warn('Task dependencies not yet implemented in API'); return []; } async getProjectDependencies(projectId) { logger.warn('Project dependencies not yet implemented in API'); return []; } async createWorkflowRule(rule) { logger.warn('Workflow rules not yet implemented in API'); return { id: 'placeholder', ...rule }; } async getWorkflowRules(filter) { logger.warn('Workflow rules not yet implemented in API'); return []; } async getWorkflowRule(ruleId) { logger.warn('Workflow rules not yet implemented in API'); return null; } async logWorkflowExecution(execution) { logger.warn('Workflow execution logging not yet implemented in API'); return { id: 'placeholder', ...execution }; } async createTriggerAutomation(automation) { logger.warn('Trigger automations not yet implemented in API'); return { id: 'placeholder', ...automation }; } async getWorkflowExecutions(filter) { logger.warn('Workflow executions not yet implemented in API'); return []; } async createDocumentCollaboration(collaboration) { logger.warn('Document collaborations not yet implemented in API'); return { id: 'placeholder', ...collaboration }; } async getDocumentCollaborations(documentId) { logger.warn('Document collaborations not yet implemented in API'); return []; } } // Export singleton instance (lazy initialization) let _apiClient = null; export function getApiClient() { if (!_apiClient) { _apiClient = new ApiClient(); } return _apiClient; } // For backward compatibility export const apiClient = new Proxy({}, { get(target, prop, receiver) { return Reflect.get(getApiClient(), prop, receiver); } }); // Maintain backward compatibility by aliasing the service export const supabaseService = apiClient;

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/jakedx6/helios9-MCP-Server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server